diff --git a/js/indices.js b/js/indices.js index b3be4bc..1c6c5db 100644 --- a/js/indices.js +++ b/js/indices.js @@ -1,794 +1,214 @@ -"use strict"; +// Tableaux globaux +let configIndices = []; // Données issues de la table "indice" +let coursActionsData = []; // Données issues de la table "cours_actions" -/* - * ========================================================= - * CONFIGURATION - * ========================================================= - */ +function chargerDonnees() { + // Chargement en parallèle des deux API + Promise.all([ + fetch("./php/api_lecture_tableindice.php").then((res) => res.json()), + fetch("./php/api_indices.php").then((res) => res.json()), + ]) + .then(([configResult, coursResult]) => { + if (configResult.status === "success") { + configIndices = configResult.data; + console.log( + "Configuration indices chargée :", + configIndices.length, + "éléments", + ); + } else { + console.error("Erreur API config indices :", configResult.message); + } -const API_URL = "../php/api_list_full_table_indice.php"; + if (coursResult.status === "success") { + coursActionsData = coursResult.data; + console.log( + "Cours actions chargés :", + coursActionsData.length, + "éléments", + ); + } else { + console.error("Erreur API cours_actions :", coursResult.message); + } -/* - * API de création. - * - * A créer côté PHP. - */ -const API_CREATE_URL = "../php/api_create_indice.php"; + // Mise à jour des vues + afficherSyntheseCartes(); + alimenterBandeauxTicker(); -const tableauIndices = document.getElementById("tableauIndices"); -const compteur = document.getElementById("compteur"); -const message = document.getElementById("message"); + // Statut de rafraîchissement + const statut = document.getElementById("statut"); + if (statut) { + statut.innerText = `Dernière mise à jour : ${new Date().toLocaleTimeString()}`; + } + }) + .catch((error) => + console.error("Erreur réseau lors du chargement des API :", error), + ); +} -const btnActualiser = document.getElementById("btnActualiser"); -const btnNouveau = document.getElementById("btnNouveau"); +function afficherSyntheseCartes() { + const container = document.getElementById("cards-container"); + if (!container) return; -const filtreIndice = document.getElementById("filtreIndice"); + container.innerHTML = ""; -/* - * ========================================================= - * MODAL - * ========================================================= - */ - -const modalNouvelEnregistrement = document.getElementById( - "modalNouvelEnregistrement", -); - -const formNouvelEnregistrement = document.getElementById( - "formNouvelEnregistrement", -); - -const btnFermerModal = document.getElementById("btnFermerModal"); -const btnAnnuler = document.getElementById("btnAnnuler"); -const btnSauvegarder = document.getElementById("btnSauvegarder"); - -const messageNouveau = document.getElementById("messageNouveau"); - -/* - * ========================================================= - * CHAMPS FORMULAIRE - * ========================================================= - */ - -const nouvelIndice = document.getElementById("nouvelIndice"); -const nouveauNom = document.getElementById("nouveauNom"); -const nouvelIsin = document.getElementById("nouvelIsin"); -const nouveauSymbole = document.getElementById("nouveauSymbole"); -const nouvelActif = document.getElementById("nouvelActif"); -const nouvelAfficher = document.getElementById("nouvelAfficher"); - -/* - * ========================================================= - * DONNEES - * ========================================================= - */ - -let tousLesEnregistrements = []; - -/* - * ========================================================= - * AFFICHER MESSAGE - * ========================================================= - */ - -function afficherMessage(texte, type = "info") { - message.textContent = texte; - - message.classList.remove( - "hidden", - "bg-emerald-900/50", - "border-emerald-700", - "text-emerald-300", - "bg-rose-900/50", - "border-rose-700", - "text-rose-300", - "bg-slate-900", - "border-slate-700", - "text-slate-300", + // CARTES : On se base STRICTEMENT sur le champ afficher == 1 + const actionsAffichees = configIndices.filter( + (item) => String(item.afficher) === "1" || item.afficher == 1, ); - message.classList.add("border"); + if (actionsAffichees.length > 0) { + actionsAffichees.forEach((conf) => { + // Croisement avec les cours en direct + const d = + coursActionsData.find( + (item) => item.symbole === conf.symbole || item.isin === conf.isin, + ) || {}; - if (type === "success") { - message.classList.add( - "bg-emerald-900/50", - "border-emerald-700", - "text-emerald-300", - ); - } else if (type === "error") { - message.classList.add("bg-rose-900/50", "border-rose-700", "text-rose-300"); - } else { - message.classList.add("bg-slate-900", "border-slate-700", "text-slate-300"); - } -} + const ecartValeur = parseFloat(d.variation || 0); + const isPositive = ecartValeur >= 0; + const badgeColor = isPositive ? "text-emerald-400" : "text-rose-400"; + const sign = isPositive ? "+" : ""; -/* - * ========================================================= - * CACHER MESSAGE - * ========================================================= - */ + const volumeFormate = d.volume + ? parseInt(d.volume).toLocaleString("fr-FR") + : "--"; -function cacherMessage() { - message.classList.add("hidden"); -} + container.innerHTML += ` +
+ +
+
+

+ ${conf.nom || conf.indice} (${conf.symbole || "N/A"}) +

+ ${conf.isin || "Aucun ISIN"} + ${conf.indice || ""} +
-/* - * ========================================================= - * ECHAPPER HTML - * ========================================================= - */ +
+
+ ${d.cours ? parseFloat(d.cours).toLocaleString("fr-FR", { minimumFractionDigits: 2 }) : "--"} +
+
+ ${sign}${ecartValeur.toLocaleString("fr-FR", { minimumFractionDigits: 2, maximumFractionDigits: 4 })} + (${sign}${parseFloat(d.variation_pct || 0).toFixed(2)}%) +
+
+ ${d.date_cours || "--/--"} +
+
+
-function echapperHTML(valeur) { - if (valeur === null || valeur === undefined) { - return ""; - } - - return String(valeur) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} - -/* - * ========================================================= - * CHECKBOX - * ========================================================= - */ - -function creerCheckbox(valeur) { - const checked = Number(valeur) === 1 || valeur === true || valeur === "1"; - - return ` -
- - - -
- `; -} - -/* - * ========================================================= - * CREER UNE LIGNE - * ========================================================= - */ - -function creerLigne(enregistrement) { - return ` - - - - ${echapperHTML(enregistrement.id)} - - - - - - ${echapperHTML(enregistrement.indice)} - - - - - - ${echapperHTML(enregistrement.nom)} - - - - - ${echapperHTML(enregistrement.isin)} - - - - - ${echapperHTML(enregistrement.symbole)} - - - - - ${creerCheckbox(enregistrement.actif)} - - - - - ${creerCheckbox(enregistrement.afficher)} - - - - - ${echapperHTML(enregistrement.updated_at)} - - - - - ${echapperHTML(enregistrement.created_at)} - - - - `; -} - -/* - * ========================================================= - * CONSTRUIRE FILTRES - * ========================================================= - */ - -function construireFiltres() { - const indices = [ - ...new Set( - tousLesEnregistrements - .map(function (enregistrement) { - return enregistrement.indice; - }) - .filter(function (indice) { - return ( - indice !== null && - indice !== undefined && - String(indice).trim() !== "" - ); - }), - ), - ]; - - indices.sort(function (a, b) { - return String(a).localeCompare(String(b), "fr", { - sensitivity: "base", + +
+
+ Haut + ${d.plus_haut ? parseFloat(d.plus_haut).toLocaleString("fr-FR", { minimumFractionDigits: 2 }) : "--"} +
+
+ Bas + ${d.plus_bas ? parseFloat(d.plus_bas).toLocaleString("fr-FR", { minimumFractionDigits: 2 }) : "--"} +
+
+ Volume + ${volumeFormate} +
+
+
+ `; }); - }); - - const filtreActuel = filtreIndice.value; - - filtreIndice.innerHTML = ""; - - /* - * Tout - */ - - const optionTout = document.createElement("option"); - - optionTout.value = "tout"; - optionTout.textContent = "Tout"; - - filtreIndice.appendChild(optionTout); - - /* - * Afficher - */ - - const optionAfficher = document.createElement("option"); - - optionAfficher.value = "afficher"; - optionAfficher.textContent = "Afficher"; - - filtreIndice.appendChild(optionAfficher); - - /* - * Inactif - */ - - const optionInactif = document.createElement("option"); - - optionInactif.value = "inactif"; - optionInactif.textContent = "Inactif"; - - filtreIndice.appendChild(optionInactif); - - /* - * Indices - */ - - indices.forEach(function (indice) { - const option = document.createElement("option"); - - option.value = "indice:" + indice; - option.textContent = indice; - - filtreIndice.appendChild(option); - }); - - /* - * Restaurer filtre - */ - - const optionExiste = Array.from(filtreIndice.options).some(function (option) { - return option.value === filtreActuel; - }); - - if (optionExiste) { - filtreIndice.value = filtreActuel; } else { - filtreIndice.value = "tout"; - } -} - -/* - * ========================================================= - * APPLIQUER FILTRE - * ========================================================= - */ - -function appliquerFiltre() { - const filtre = filtreIndice.value; - - let enregistrementsFiltres; - - /* - * Tout - */ - - if (filtre === "tout") { - enregistrementsFiltres = tousLesEnregistrements; - } else if (filtre === "afficher") { - /* - * Afficher - */ - enregistrementsFiltres = tousLesEnregistrements.filter( - function (enregistrement) { - return ( - Number(enregistrement.afficher) === 1 || - enregistrement.afficher === true || - enregistrement.afficher === "1" - ); - }, - ); - } else if (filtre === "inactif") { - /* - * Inactif - */ - enregistrementsFiltres = tousLesEnregistrements.filter( - function (enregistrement) { - return !( - Number(enregistrement.actif) === 1 || - enregistrement.actif === true || - enregistrement.actif === "1" - ); - }, - ); - } else if (filtre.startsWith("indice:")) { - /* - * Indice - */ - const indiceRecherche = filtre.substring("indice:".length); - - enregistrementsFiltres = tousLesEnregistrements.filter( - function (enregistrement) { - return ( - String(enregistrement.indice).toLowerCase() === - String(indiceRecherche).toLowerCase() - ); - }, - ); - } else { - enregistrementsFiltres = tousLesEnregistrements; - } - - afficherDonneesFiltrees(enregistrementsFiltres); -} - -/* - * ========================================================= - * AFFICHER DONNEES FILTREES - * ========================================================= - */ - -function afficherDonneesFiltrees(enregistrements) { - tableauIndices.innerHTML = ""; - - if (!Array.isArray(enregistrements) || enregistrements.length === 0) { - compteur.textContent = "0 enregistrement"; - - tableauIndices.innerHTML = ` - - - - Aucun enregistrement correspondant - au filtre sélectionné. - - - + container.innerHTML = ` +
+ Aucune action configurée à 'afficher = 1'. +
`; - - return; } - - /* - * ======================================================= - * TRI PAR ID CROISSANT - * ======================================================= - */ - - const donneesTriees = [...enregistrements].sort(function (a, b) { - return Number(a.id) - Number(b.id); - }); - - let html = ""; - - donneesTriees.forEach(function (enregistrement) { - html += creerLigne(enregistrement); - }); - - tableauIndices.innerHTML = html; - - compteur.textContent = - donneesTriees.length + - (donneesTriees.length > 1 ? " enregistrements" : " enregistrement"); } -/* - * ========================================================= - * AFFICHER DONNEES - * ========================================================= - */ +function alimenterBandeauxTicker() { + const preparerDonneesTicker = (nomIndiceRecherche) => { + const cleanSearch = nomIndiceRecherche + .replace(/[^a-z0-9]/gi, "") + .toLowerCase(); -function afficherDonnees(data) { - tousLesEnregistrements = Array.isArray(data) ? data : []; - - construireFiltres(); - - appliquerFiltre(); -} - -/* - * ========================================================= - * CHARGER DONNEES - * ========================================================= - */ - -async function chargerDonnees() { - btnActualiser.disabled = true; - - btnActualiser.textContent = "↻ Chargement..."; - - afficherMessage("Chargement des indices et des actions...", "info"); - - try { - const response = await fetch(API_URL, { - method: "GET", - - credentials: "same-origin", - - cache: "no-store", - - headers: { - Accept: "application/json", - }, + // BANDEAUX : On prend l'intégralité des actions disponibles pour l'indice (via coursActionsData ou configIndices) + let liste = coursActionsData.filter((item) => { + if (!item.indice) return false; + const cleanItemIndice = item.indice + .replace(/[^a-z0-9]/gi, "") + .toLowerCase(); + return cleanItemIndice === cleanSearch; }); - if (!response.ok) { - throw new Error("Erreur HTTP " + response.status); + if (liste.length === 0) { + liste = configIndices.filter((item) => { + if (!item.indice) return false; + const cleanItemIndice = item.indice + .replace(/[^a-z0-9]/gi, "") + .toLowerCase(); + return cleanItemIndice === cleanSearch; + }); } - const result = await response.json(); + // Fusion et formatage + let listeFinale = liste.map((conf) => { + let coursMatch = + coursActionsData.find( + (item) => item.symbole === conf.symbole || item.isin === conf.isin, + ) || conf; + return { + nom: conf.nom || coursMatch.nom || conf.symbole, + cours: coursMatch.cours || 0, + variation_pct: coursMatch.variation_pct || 0, + }; + }); - if (!result || result.status !== "success") { - throw new Error( - result && result.message - ? result.message - : "L'API a retourné une erreur.", - ); - } + // Tri alphabétique + listeFinale.sort((a, b) => a.nom.localeCompare(b.nom)); - afficherDonnees(result.data); - - cacherMessage(); - } catch (error) { - console.error("Erreur chargement indices/actions :", error); - - tableauIndices.innerHTML = ` - - - - Impossible de charger les données. - - - - `; - - compteur.textContent = "Erreur de chargement"; - - afficherMessage( - "Erreur lors de la récupération des données : " + error.message, - "error", - ); - } finally { - btnActualiser.disabled = false; - - btnActualiser.textContent = "↻ Actualiser"; - } -} - -/* - * ========================================================= - * OUVRIR MODAL - * ========================================================= - */ - -function ouvrirModal() { - formNouvelEnregistrement.reset(); - - /* - * Valeurs par défaut - */ - - nouvelActif.checked = true; - nouvelAfficher.checked = true; - - messageNouveau.classList.add("hidden"); - - modalNouvelEnregistrement.classList.remove("hidden"); - - modalNouvelEnregistrement.classList.add("flex"); - - setTimeout(function () { - nouvelIndice.focus(); - }, 50); -} - -/* - * ========================================================= - * FERMER MODAL - * ========================================================= - */ - -function fermerModal() { - modalNouvelEnregistrement.classList.add("hidden"); - - modalNouvelEnregistrement.classList.remove("flex"); -} - -/* - * ========================================================= - * MESSAGE MODAL - * ========================================================= - */ - -function afficherMessageNouveau(texte, type = "error") { - messageNouveau.textContent = texte; - - messageNouveau.classList.remove( - "hidden", - "bg-rose-900/50", - "border-rose-700", - "text-rose-300", - "bg-emerald-900/50", - "border-emerald-700", - "text-emerald-300", - ); - - messageNouveau.classList.add("border"); - - if (type === "success") { - messageNouveau.classList.add( - "bg-emerald-900/50", - "border-emerald-700", - "text-emerald-300", - ); - } else { - messageNouveau.classList.add( - "bg-rose-900/50", - "border-rose-700", - "text-rose-300", - ); - } -} - -/* - * ========================================================= - * SAUVEGARDER NOUVEL ENREGISTREMENT - * ========================================================= - */ - -async function sauvegarderNouvelEnregistrement(event) { - event.preventDefault(); - - /* - * Lecture des champs - */ - - const donnees = { - indice: nouvelIndice.value.trim(), - - nom: nouveauNom.value.trim(), - - isin: nouvelIsin.value.trim().toUpperCase(), - - symbole: nouveauSymbole.value.trim().toUpperCase(), - - actif: nouvelActif.checked ? 1 : 0, - - afficher: nouvelAfficher.checked ? 1 : 0, + return listeFinale; }; - /* - * Vérification - */ + remplirUnBandeau("ticker-cac40", preparerDonneesTicker("CAC40")); + remplirUnBandeau("ticker-ibex35", preparerDonneesTicker("IBEX35")); + remplirUnBandeau("ticker-dowjones", preparerDonneesTicker("DOWJONES")); +} - if (!donnees.indice || !donnees.nom || !donnees.isin || !donnees.symbole) { - afficherMessageNouveau("Tous les champs texte sont obligatoires."); +function remplirUnBandeau(elementId, donneesTriees) { + const container = document.getElementById(elementId); + if (!container) return; + if (donneesTriees.length === 0) { + container.innerHTML = `Aucune donnée disponible`; return; } - /* - * Désactivation bouton - */ - - btnSauvegarder.disabled = true; - - btnSauvegarder.textContent = "Sauvegarde..."; - - messageNouveau.classList.add("hidden"); - - try { - const response = await fetch(API_CREATE_URL, { - method: "POST", - - credentials: "same-origin", - - headers: { - "Content-Type": "application/json", - - Accept: "application/json", - }, - - body: JSON.stringify(donnees), + let blocs = ""; + donneesTriees.forEach((item) => { + const isPositive = parseFloat(item.variation_pct || 0) >= 0; + const colorClass = isPositive ? "text-emerald-400" : "text-rose-400"; + const sign = isPositive ? "+" : ""; + const coursFormate = parseFloat(item.cours || 0).toLocaleString("fr-FR", { + minimumFractionDigits: 2, }); + const variationFormatee = parseFloat(item.variation_pct || 0).toFixed(2); - if (!response.ok) { - throw new Error("Erreur HTTP " + response.status); - } + blocs += ` +
+ ${item.nom} + ${coursFormate} + ${sign}${variationFormatee}% +
+ `; + }); - const result = await response.json(); - - if (!result || result.status !== "success") { - throw new Error( - result && result.message - ? result.message - : "Impossible de créer l'enregistrement.", - ); - } - - /* - * Fermeture - */ - - fermerModal(); - - /* - * Message - */ - - afficherMessage("L'enregistrement a été créé avec succès.", "success"); - - /* - * Rechargement - */ - - await chargerDonnees(); - } catch (error) { - console.error("Erreur création enregistrement :", error); - - afficherMessageNouveau(error.message, "error"); - } finally { - btnSauvegarder.disabled = false; - - btnSauvegarder.textContent = "Sauvegarder"; - } + container.innerHTML = blocs + blocs + blocs; + const dureeTotale = Math.max(20, donneesTriees.length * 4); + container.style.animationDuration = `${dureeTotale}s`; } -/* - * ========================================================= - * EVENEMENTS - * ========================================================= - */ - -filtreIndice.addEventListener("change", function () { - appliquerFiltre(); -}); - -btnActualiser.addEventListener("click", function () { - chargerDonnees(); -}); - -btnNouveau.addEventListener("click", function () { - ouvrirModal(); -}); - -btnFermerModal.addEventListener("click", function () { - fermerModal(); -}); - -btnAnnuler.addEventListener("click", function () { - fermerModal(); -}); - -formNouvelEnregistrement.addEventListener( - "submit", - sauvegarderNouvelEnregistrement, -); - -/* - * Fermer en cliquant sur le fond - */ - -modalNouvelEnregistrement.addEventListener("click", function (event) { - if (event.target === modalNouvelEnregistrement) { - fermerModal(); - } -}); - -/* - * Touche Echap - */ - -document.addEventListener("keydown", function (event) { - if ( - event.key === "Escape" && - !modalNouvelEnregistrement.classList.contains("hidden") - ) { - fermerModal(); - } -}); - -/* - * ========================================================= - * INITIALISATION - * ========================================================= - */ - -document.addEventListener("DOMContentLoaded", function () { - chargerDonnees(); -}); +// Démarrage +chargerDonnees(); +setInterval(chargerDonnees, 60000);