fix: improve UI layout and add loading indicator for events
This commit is contained in:
249
index.html
249
index.html
@@ -10,9 +10,11 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<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;">
|
<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>
|
<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>
|
<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>
|
||||||
|
|
||||||
<div class="container-fluid d-flex align-items-center justify-content-center">
|
<div class="container-fluid d-flex align-items-center justify-content-center">
|
||||||
@@ -21,7 +23,7 @@
|
|||||||
|
|
||||||
<div id="loginContainer" class="mb-3">
|
<div id="loginContainer" class="mb-3">
|
||||||
<div id="oidcLoginSection">
|
<div id="oidcLoginSection">
|
||||||
<button id="oidcLoginBtn" class="btn btn-primary btn-lg px-4 py-3 shadow">Se connecter avec Infomaniak</button>
|
<button id="oidcLoginBtn" class="btn btn-primary btn-md px-4 py-3 shadow">Se connecter</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,25 +32,26 @@
|
|||||||
<div class="container mt-2" id="mainContent" style="display: none;">
|
<div class="container mt-2" id="mainContent" style="display: none;">
|
||||||
|
|
||||||
<div id="eventFilters" style="display: none;">
|
<div id="eventFilters" style="display: none;">
|
||||||
<div class="row g-2 align-items-end">
|
<div class="mb-3">
|
||||||
<div class="col-auto">
|
<label for="account" class="form-label">Compte</label>
|
||||||
<label for="account" class="form-label mb-1">Compte</label>
|
<select id="account" class="form-select">
|
||||||
<select id="account" class="form-select form-select-sm">
|
<option value="default">Défaut</option>
|
||||||
<option value="default">Défaut</option>
|
</select>
|
||||||
</select>
|
<label for="agegroup" class="form-label">Âge</label>
|
||||||
</div>
|
<select id="agegroup" class="form-select">
|
||||||
<div class="col-auto">
|
<option value="">Tous</option>
|
||||||
<label for="agegroup" class="form-label mb-1">Âge</label>
|
</select>
|
||||||
<select id="agegroup" class="form-select form-select-sm">
|
<button id="fetchEvents" class="btn btn-primary" style="margin-top: 1.2rem;">Charger</button>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
|
<div id="loadingIndicator" class="text-center my-3" style="display: none;">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Chargement...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">Chargement des événements...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="eventList" class="row mt-2"></div>
|
<div id="eventList" class="row mt-2"></div>
|
||||||
|
|
||||||
<!-- Modal pour afficher les détails d'un événement -->
|
<!-- Modal pour afficher les détails d'un événement -->
|
||||||
@@ -66,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const loginContainer = document.getElementById("loginContainer");
|
const loginContainer = document.getElementById("loginContainer");
|
||||||
const oidcLoginSection = document.getElementById("oidcLoginSection");
|
const oidcLoginSection = document.getElementById("oidcLoginSection");
|
||||||
@@ -89,17 +92,17 @@
|
|||||||
fetch(`${apiBaseUrl}/userinfo`, {
|
fetch(`${apiBaseUrl}/userinfo`, {
|
||||||
headers: { "Authorization": `Bearer ${storedApiKey}` }
|
headers: { "Authorization": `Bearer ${storedApiKey}` }
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(userData => {
|
.then(userData => {
|
||||||
userInfo = {email: userData.email || "Utilisateur connecté"};
|
userInfo = { email: userData.email || "Utilisateur connecté" };
|
||||||
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
||||||
renderLoginSection();
|
renderLoginSection();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error("Error fetching user info:", e);
|
console.error("Error fetching user info:", e);
|
||||||
// If we can't fetch user info, proceed with rendering
|
// If we can't fetch user info, proceed with rendering
|
||||||
renderLoginSection();
|
renderLoginSection();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Render the login section normally
|
// Render the login section normally
|
||||||
renderLoginSection();
|
renderLoginSection();
|
||||||
@@ -136,49 +139,49 @@
|
|||||||
fetch(`${apiBaseUrl}/userinfo`, {
|
fetch(`${apiBaseUrl}/userinfo`, {
|
||||||
headers: { "Authorization": `Bearer ${accessToken}` }
|
headers: { "Authorization": `Bearer ${accessToken}` }
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(userData => {
|
.then(userData => {
|
||||||
userInfo = {email: userData.email || "Utilisateur connecté"};
|
userInfo = { email: userData.email || "Utilisateur connecté" };
|
||||||
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
renderLoginSection();
|
renderLoginSection();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error("Error fetching user info:", e);
|
console.error("Error fetching user info:", e);
|
||||||
// Fallback to parsing token as JWT
|
// Fallback to parsing token as JWT
|
||||||
try {
|
try {
|
||||||
// First, try to parse as JWT
|
// First, try to parse as JWT
|
||||||
const tokenParts = accessToken.split('.');
|
const tokenParts = accessToken.split('.');
|
||||||
let userData = {};
|
let userData = {};
|
||||||
|
|
||||||
if (tokenParts.length === 3) {
|
if (tokenParts.length === 3) {
|
||||||
// Standard JWT format
|
// Standard JWT format
|
||||||
const payload = tokenParts[1];
|
const payload = tokenParts[1];
|
||||||
if (payload) {
|
if (payload) {
|
||||||
// Add padding if needed
|
// Add padding if needed
|
||||||
const paddedPayload = payload.padEnd(payload.length + (4 - payload.length % 4) % 4, '=');
|
const paddedPayload = payload.padEnd(payload.length + (4 - payload.length % 4) % 4, '=');
|
||||||
const decodedPayload = atob(paddedPayload);
|
const decodedPayload = atob(paddedPayload);
|
||||||
userData = JSON.parse(decodedPayload);
|
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 };
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Non-JWT token, treat as opaque token
|
userInfo = { email: userData.email || "Utilisateur connecté" };
|
||||||
console.log("Non-JWT token received, using default user info");
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
||||||
userData = {email: "utilisateur@" + window.location.hostname};
|
} catch (parseError) {
|
||||||
|
console.error("Error decoding token:", parseError);
|
||||||
|
// Fallback to a generic user object
|
||||||
|
userInfo = { email: "Utilisateur connecté" };
|
||||||
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfo = {email: userData.email || "Utilisateur connecté"};
|
// Update UI
|
||||||
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
renderLoginSection();
|
||||||
} 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
|
// Remove token from URL
|
||||||
window.history.replaceState({}, document.title, "/");
|
window.history.replaceState({}, document.title, "/");
|
||||||
@@ -257,58 +260,58 @@
|
|||||||
fetch(`${apiBaseUrl}/accounts`, {
|
fetch(`${apiBaseUrl}/accounts`, {
|
||||||
headers: { "Authorization": `Bearer ${storedApiKey}` }
|
headers: { "Authorization": `Bearer ${storedApiKey}` }
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return response.json().then(errorData => {
|
return response.json().then(errorData => {
|
||||||
console.error("Accounts error response:", errorData);
|
console.error("Accounts error response:", errorData);
|
||||||
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.detail || 'Unknown error'}`);
|
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);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
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 = '';
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
// If no accounts are available, add a default option
|
// Set the selected account in the dropdown
|
||||||
if (accounts.length === 0) {
|
accountSelect.value = accountToSelect;
|
||||||
const option = document.createElement("option");
|
})
|
||||||
option.value = "default";
|
.catch(error => {
|
||||||
option.textContent = "Default";
|
console.error("Erreur lors du chargement des comptes:", error);
|
||||||
accountSelect.appendChild(option);
|
alert("Accès refusé. Vous n'êtes pas autorisé à accéder à cette application. Veuillez contacter l'administrateur.");
|
||||||
return;
|
logout();
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
renderLoginSection();
|
||||||
@@ -335,6 +338,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function fetchEvents(apiKey, account) {
|
function fetchEvents(apiKey, account) {
|
||||||
|
// Show loading indicator
|
||||||
|
const loadingIndicator = document.getElementById("loadingIndicator");
|
||||||
|
const eventList = document.getElementById("eventList");
|
||||||
|
loadingIndicator.style.display = "block";
|
||||||
|
eventList.innerHTML = ""; // Clear previous events
|
||||||
|
|
||||||
fetch(`${apiBaseUrl}/schedule?account=${account}`, {
|
fetch(`${apiBaseUrl}/schedule?account=${account}`, {
|
||||||
headers: { "Authorization": `Bearer ${apiKey}` }
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
||||||
})
|
})
|
||||||
@@ -370,6 +379,9 @@
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
// Hide loading indicator
|
||||||
|
loadingIndicator.style.display = "none";
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// Check if data is an array
|
// Check if data is an array
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
@@ -383,6 +395,9 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
// Hide loading indicator on error
|
||||||
|
loadingIndicator.style.display = "none";
|
||||||
|
|
||||||
console.error("Erreur lors du chargement des événements:", error);
|
console.error("Erreur lors du chargement des événements:", error);
|
||||||
alert("Erreur lors du chargement des événements: " + error.message);
|
alert("Erreur lors du chargement des événements: " + error.message);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user