diff --git a/images/GFBlanc.png b/images/GFBlanc.png new file mode 100644 index 0000000..f0a2322 Binary files /dev/null and b/images/GFBlanc.png differ diff --git a/images/dollars.ico b/images/dollars.ico new file mode 100644 index 0000000..bbd62c3 Binary files /dev/null and b/images/dollars.ico differ diff --git a/index.html b/index.html index 21044c5..af761ce 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ Suivi des Indices Boursiers + @@ -12,66 +13,50 @@
- +
-
-

- Tableau de Bord Boursier -

-

- CAC 40, IBEX 35 & Dow Jones en temps réel -

-
-
- Chargement des données... +
+ +
+ Logo Giraud Finance +
+ + +
+

+ Tableau de bord boursier +

+

+ Suivi des cours, variations et analyses de vos portefeuilles et + indices financiers. +

+
+ + +
+ + Dernière mise à jour : --:--:-- + +
- -
+ +
- - -
-
-

- Historique récent des cotations -

- 0 enregistrements -
-
- - - - - - - - - - - - - -
IndiceNomCoursVariation (%)Date
-
-
@@ -84,7 +69,6 @@ >
-
CAC40
-
response.json()) - .then((result) => { - if (result.status === "success") { - // Stockage dans le tableau global JS - indicesData = result.data; - - // Mise à jour de tous les affichages - afficherSyntheseCartes(); - afficherTableauDetails(); - alimenterBandeauxTicker(); - - // Mise à jour du statut - document.getElementById("statut").innerText = - `Dernière mise à jour : ${new Date().toLocaleTimeString()}`; - document.getElementById("counter").innerText = - `${indicesData.length} enregistrements`; + // 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 :", result.message); + console.error("Erreur API config indices :", configResult.message); + } + + 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); + } + + // Mise à jour des vues + afficherSyntheseCartes(); + alimenterBandeauxTicker(); + + // 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 :", error)); + .catch((error) => + console.error("Erreur réseau lors du chargement des API :", error), + ); } function afficherSyntheseCartes() { const container = document.getElementById("cards-container"); + if (!container) return; + container.innerHTML = ""; - // Trouver les indices uniques présents dans le tableau - const indicesUniques = [...new Set(indicesData.map((item) => item.indice))]; + // CARTES : On se base STRICTEMENT sur le champ afficher == 1 + const actionsAffichees = configIndices.filter( + (item) => String(item.afficher) === "1" || item.afficher == 1, + ); - indicesUniques.forEach((nomIndice) => { - // Récupère le premier élément (le plus récent) pour cet indice - const dernier = indicesData.find((item) => item.indice === nomIndice); - if (!dernier) return; + 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, + ) || {}; - const isPositive = parseFloat(dernier.variation_pct) >= 0; - const badgeColor = isPositive - ? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20" - : "bg-rose-500/10 text-rose-400 border-rose-500/20"; - const sign = isPositive ? "+" : ""; + const ecartValeur = parseFloat(d.variation || 0); + const isPositive = ecartValeur >= 0; + const badgeColor = isPositive ? "text-emerald-400" : "text-rose-400"; + const sign = isPositive ? "+" : ""; - container.innerHTML += ` -
-
-
- ${dernier.indice} - - ${sign}${parseFloat(dernier.variation_pct).toFixed(2)}% - -
-

${dernier.nom || dernier.indice}

+ const volumeFormate = d.volume + ? parseInt(d.volume).toLocaleString("fr-FR") + : "--"; + + container.innerHTML += ` +
+ +
+
+

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

+ ${conf.isin || "Aucun ISIN"} + ${conf.indice || ""}
-
- ${parseFloat(dernier.cours).toLocaleString("fr-FR")} - ${dernier.date_cours} + +
+
+ ${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 afficherTableauDetails() { - const tbody = document.getElementById("table-body"); - tbody.innerHTML = ""; - - if (indicesData.length === 0) { - tbody.innerHTML = `Aucune donnée disponible`; - return; + +
+
+ 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} +
+
+
+ `; + }); + } else { + container.innerHTML = ` +
+ Aucune action configurée à 'afficher = 1'. +
+ `; } - - indicesData.forEach((row) => { - const isPositive = parseFloat(row.variation_pct) >= 0; - const textColor = isPositive ? "text-emerald-400" : "text-rose-400"; - const sign = isPositive ? "+" : ""; - - tbody.innerHTML += ` - - ${row.indice} - ${row.nom || "-"} - ${parseFloat(row.cours).toLocaleString("fr-FR")} - ${sign}${parseFloat(row.variation_pct).toFixed(2)}% - ${row.date_cours} - - `; - }); } function alimenterBandeauxTicker() { - const preparerDonneesTicker = (nomIndice) => { - let liste = indicesData.filter((item) => item.indice === nomIndice); + const preparerDonneesTicker = (nomIndiceRecherche) => { + const cleanSearch = nomIndiceRecherche + .replace(/[^a-z0-9]/gi, "") + .toLowerCase(); - // Tri alphabétique - liste.sort((a, b) => { - let valA = (a.nom || a.indice).toLowerCase(); - let valB = (b.nom || b.indice).toLowerCase(); - return valA.localeCompare(valB); + // 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; }); - return liste; + 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; + }); + } + + // 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, + }; + }); + + // Tri alphabétique + listeFinale.sort((a, b) => a.nom.localeCompare(b.nom)); + + return listeFinale; }; remplirUnBandeau("ticker-cac40", preparerDonneesTicker("CAC40")); @@ -112,46 +178,37 @@ function alimenterBandeauxTicker() { function remplirUnBandeau(elementId, donneesTriees) { const container = document.getElementById(elementId); - if (!container) { - console.error("Élément introuvable :", elementId); - return; - } + if (!container) return; if (donneesTriees.length === 0) { - container.innerHTML = `Aucune donnée disponible`; + container.innerHTML = `Aucune donnée disponible`; return; } let blocs = ""; donneesTriees.forEach((item) => { - const isPositive = parseFloat(item.variation_pct) >= 0; + const isPositive = parseFloat(item.variation_pct || 0) >= 0; const colorClass = isPositive ? "text-emerald-400" : "text-rose-400"; const sign = isPositive ? "+" : ""; - const nomAffichage = item.nom || item.indice; - const coursFormate = parseFloat(item.cours).toLocaleString("fr-FR"); - const variationFormatee = parseFloat(item.variation_pct).toFixed(2); + const coursFormate = parseFloat(item.cours || 0).toLocaleString("fr-FR", { + minimumFractionDigits: 2, + }); + const variationFormatee = parseFloat(item.variation_pct || 0).toFixed(2); blocs += ` -
- ${nomAffichage} - ${coursFormate} - ${sign}${variationFormatee}% -
- `; +
+ ${item.nom} + ${coursFormate} + ${sign}${variationFormatee}% +
+ `; }); - // Duplication du contenu pour la boucle infinie fluide - container.innerHTML = blocs + blocs; - - // Vitesse uniforme et lente - const dureeParElement = 6; - const dureeTotale = Math.max(30, donneesTriees.length * dureeParElement); - + container.innerHTML = blocs + blocs + blocs; + const dureeTotale = Math.max(20, donneesTriees.length * 4); container.style.animationDuration = `${dureeTotale}s`; } -// Lancer le chargement des données au démarrage de la page +// Démarrage chargerDonnees(); - -// Rafraîchir automatiquement toutes les 60 secondes setInterval(chargerDonnees, 60000); diff --git a/php/api_lecture_tableindice.php b/php/api_lecture_tableindice.php new file mode 100644 index 0000000..1c2dd9f --- /dev/null +++ b/php/api_lecture_tableindice.php @@ -0,0 +1,28 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // Requête pour récupérer les actions configurées avec afficher = 1 dans la table indice + $stmt = $pdo->prepare("SELECT * FROM indice WHERE afficher = 1"); + $stmt->execute(); + $actionsConfig = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode([ + "status" => "success", + "data" => $actionsConfig + ]); +} catch (PDOException $e) { + echo json_encode([ + "status" => "error", + "message" => $e->getMessage() + ]); +}