Menu 1 2 3 4 5 7 8 ok
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
{% extends "base.html" %} {% block title %}Import Historique CSV - Bolsa{% endblock
|
||||
%} {% block header_title %}Import historique{% endblock %} {% block content %}
|
||||
<div class="flex flex-col items-center justify-center py-6 relative">
|
||||
<!-- Système de Toasts -->
|
||||
<div
|
||||
id="toast-container"
|
||||
class="fixed top-6 left-1/2 transform -translate-x-1/2 z-50 space-y-3 w-full max-w-md px-4 pointer-events-none"
|
||||
>
|
||||
{% set messages = get_flashed_messages(with_categories=true) %} {% if messages
|
||||
%} {% for category, message in messages %}
|
||||
<div
|
||||
class="toast-message pointer-events-auto flex items-center justify-center px-4 py-3 rounded-xl shadow-2xl text-white text-sm font-medium transition-all duration-300 transform translate-y-0 opacity-100 text-center {% if category == 'success' %} bg-emerald-600 border border-emerald-500 {% elif category == 'warning' %} bg-amber-600 border border-amber-500 {% else %} bg-rose-600 border border-rose-500 {% endif %}"
|
||||
>
|
||||
<span>{{ message }}</span>
|
||||
</div>
|
||||
{% endfor %} {% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Box centrée principale -->
|
||||
<div
|
||||
class="bg-gray-900 border border-gray-800 p-8 rounded-2xl shadow-2xl w-full max-w-3xl overflow-hidden"
|
||||
>
|
||||
<!-- 1. En-tête -->
|
||||
<div
|
||||
class="flex justify-between items-center px-2 py-2 mb-6 border-b border-gray-800 pb-4"
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-xl font-extrabold text-white flex items-center gap-2">
|
||||
<i class="fa-solid fa-chart-line text-blue-500"></i> Import Historique
|
||||
CSV
|
||||
</h2>
|
||||
<p class="text-gray-400 text-sm mt-0.5">
|
||||
Import du cours historique d'une action (OHLCV) par ISIN.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="{{ url_for('main.menu') }}"
|
||||
class="px-4 py-2 bg-gray-800 hover:bg-gray-700 text-gray-200 text-sm font-medium rounded-lg transition duration-200"
|
||||
>
|
||||
Retour au menu
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 2. Corps : Box d'importation -->
|
||||
<form
|
||||
id="form-import-historique"
|
||||
action="{{ url_for('main.import_historique_csv') }}"
|
||||
method="POST"
|
||||
enctype="multipart/form-data"
|
||||
class="space-y-6"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
|
||||
<div
|
||||
class="bg-gray-950 border border-gray-800 rounded-xl p-6 space-y-5"
|
||||
>
|
||||
<h3
|
||||
class="text-sm font-semibold text-gray-200 uppercase tracking-wider"
|
||||
>
|
||||
Paramètres d'importation
|
||||
</h3>
|
||||
|
||||
<!-- 1. ISIN + vérification dynamique -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-400 mb-2"
|
||||
>Code ISIN de l'action</label
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
id="input-isin"
|
||||
name="isin"
|
||||
required
|
||||
placeholder="Ex : NL00150001Q9"
|
||||
class="flex-1 bg-gray-900 border border-gray-800 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-blue-500 uppercase"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="btn-verifier-isin"
|
||||
onclick="verifierIsin()"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium rounded-lg transition cursor-pointer"
|
||||
>
|
||||
<i class="fa-solid fa-check"></i> Vérifier
|
||||
</button>
|
||||
</div>
|
||||
<p id="isin-status" class="text-xs mt-2"></p>
|
||||
</div>
|
||||
|
||||
<!-- 2. Nom de l'action (lecture seule, affiché après vérification) -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-400 mb-2"
|
||||
>Nom de l'action</label
|
||||
>
|
||||
<div
|
||||
class="flex items-center bg-gray-950 border border-gray-800 rounded-lg px-3 py-2.5"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="input-company-name"
|
||||
readonly
|
||||
placeholder="Sera affiché après vérification de l'ISIN..."
|
||||
class="flex-1 bg-transparent text-amber-400 font-semibold text-sm focus:outline-none cursor-default"
|
||||
/>
|
||||
<div
|
||||
id="action-info"
|
||||
class="hidden text-xs text-gray-300 ml-3 flex items-center gap-3"
|
||||
>
|
||||
<span><strong>Ticker :</strong> <span id="lbl-ticker"></span></span>
|
||||
<span class="text-gray-600">|</span>
|
||||
<span><strong>Devise :</strong> <span id="lbl-currency"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Sélection du fichier CSV -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-400 mb-2"
|
||||
>Fichier CSV historique</label
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
accept=".csv"
|
||||
id="input-file"
|
||||
required
|
||||
class="block w-full text-xs text-gray-400 file:mr-4 file:py-3 file:px-4 file:rounded-xl file:border-0 file:text-sm file:font-semibold file:bg-blue-600 file:text-white hover:file:bg-blue-500 file:cursor-pointer cursor-pointer bg-gray-900 border border-gray-800 rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Note sur le format attendu -->
|
||||
<div
|
||||
class="bg-amber-500/10 border border-amber-500/30 rounded-lg p-3 text-amber-300 text-xs flex items-start space-x-2"
|
||||
>
|
||||
<i class="fa-solid fa-circle-info text-amber-400 mt-0.5"></i>
|
||||
<span>
|
||||
Format CSV attendu (en-tête obligatoire, séparateur virgule) :
|
||||
<code
|
||||
class="text-amber-200 bg-gray-900 px-1.5 py-0.5 rounded font-mono"
|
||||
>date,price,open,hight,low,vol,change</code
|
||||
>. La colonne <code class="font-mono">vol</code> accepte les suffixes
|
||||
<code class="font-mono">K</code> (x1000) et
|
||||
<code class="font-mono">M</code> (x1 000 000). Les doublons (même
|
||||
ISIN + même date) sont ignorés automatiquement.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. Bouton de validation -->
|
||||
<div class="flex items-center justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onclick="reinitialiser()"
|
||||
class="px-4 py-2.5 bg-gray-800 hover:bg-gray-700 text-gray-300 text-sm font-medium rounded-xl transition"
|
||||
>
|
||||
<i class="fa-solid fa-rotate-left"></i> Réinitialiser
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
id="btn-valider"
|
||||
disabled
|
||||
class="px-5 py-2.5 bg-emerald-600/50 cursor-not-allowed text-white text-sm font-semibold rounded-xl shadow-lg transition"
|
||||
>
|
||||
<i class="fa-solid fa-upload"></i> Importer le fichier
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 3. Pied de boîte -->
|
||||
<div
|
||||
class="px-2 py-4 mt-6 text-xs text-gray-400 italic text-center"
|
||||
>
|
||||
Module d'import historique sécurisé — Giraud Finance - 2026
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Disparition automatique des Toasts au bout de 4 secondes
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const toasts = document.querySelectorAll(".toast-message");
|
||||
toasts.forEach((toast) => {
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = "0";
|
||||
toast.style.transform = "translateY(10px)";
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 4000);
|
||||
});
|
||||
});
|
||||
|
||||
// Vérification dynamique de l'ISIN
|
||||
function verifierIsin() {
|
||||
const isin = document.getElementById("input-isin").value.trim();
|
||||
const statusEl = document.getElementById("isin-status");
|
||||
const infoBox = document.getElementById("action-info");
|
||||
const companyInput = document.getElementById("input-company-name");
|
||||
const btnValider = document.getElementById("btn-valider");
|
||||
|
||||
if (!isin) {
|
||||
statusEl.textContent = "Veuillez saisir un code ISIN.";
|
||||
statusEl.className = "text-xs mt-2 text-rose-400";
|
||||
btnValider.disabled = true;
|
||||
btnValider.className =
|
||||
"px-5 py-2.5 bg-emerald-600/50 cursor-not-allowed text-white text-sm font-semibold rounded-xl shadow-lg transition";
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("/api/verifier_isin", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ isin: isin }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.exists) {
|
||||
statusEl.innerHTML =
|
||||
'<i class="fa-solid fa-circle-check"></i> ISIN reconnu dans la base.';
|
||||
statusEl.className = "text-xs mt-2 text-emerald-400";
|
||||
companyInput.value = data.company_name || "";
|
||||
document.getElementById("lbl-ticker").textContent =
|
||||
data.ticker || "N/A";
|
||||
document.getElementById("lbl-currency").textContent =
|
||||
data.currency || "N/A";
|
||||
infoBox.classList.remove("hidden");
|
||||
|
||||
btnValider.disabled = false;
|
||||
btnValider.className =
|
||||
"px-5 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-semibold rounded-xl shadow-lg transition cursor-pointer";
|
||||
} else {
|
||||
statusEl.innerHTML =
|
||||
'<i class="fa-solid fa-circle-xmark"></i> Cet ISIN n\'existe pas dans la base actions.';
|
||||
statusEl.className = "text-xs mt-2 text-rose-400";
|
||||
companyInput.value = "";
|
||||
infoBox.classList.add("hidden");
|
||||
|
||||
btnValider.disabled = true;
|
||||
btnValider.className =
|
||||
"px-5 py-2.5 bg-emerald-600/50 cursor-not-allowed text-white text-sm font-semibold rounded-xl shadow-lg transition";
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
statusEl.textContent = "Erreur de communication avec le serveur.";
|
||||
statusEl.className = "text-xs mt-2 text-rose-400";
|
||||
});
|
||||
}
|
||||
|
||||
// Validation du formulaire : ISIN vérifié + fichier sélectionné
|
||||
document
|
||||
.getElementById("input-file")
|
||||
.addEventListener("change", verifierBoutonValider);
|
||||
|
||||
function verifierBoutonValider() {
|
||||
const isinOk =
|
||||
document.getElementById("input-company-name").value !== "";
|
||||
const fichierOk =
|
||||
document.getElementById("input-file").value !== "";
|
||||
const btnValider = document.getElementById("btn-valider");
|
||||
|
||||
if (isinOk && fichierOk) {
|
||||
btnValider.disabled = false;
|
||||
btnValider.className =
|
||||
"px-5 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-semibold rounded-xl shadow-lg transition cursor-pointer";
|
||||
} else {
|
||||
btnValider.disabled = true;
|
||||
btnValider.className =
|
||||
"px-5 py-2.5 bg-emerald-600/50 cursor-not-allowed text-white text-sm font-semibold rounded-xl shadow-lg transition";
|
||||
}
|
||||
}
|
||||
|
||||
function reinitialiser() {
|
||||
document.getElementById("form-import-historique").reset();
|
||||
document.getElementById("isin-status").textContent = "";
|
||||
document.getElementById("action-info").classList.add("hidden");
|
||||
const btnValider = document.getElementById("btn-valider");
|
||||
btnValider.disabled = true;
|
||||
btnValider.className =
|
||||
"px-5 py-2.5 bg-emerald-600/50 cursor-not-allowed text-white text-sm font-semibold rounded-xl shadow-lg transition";
|
||||
}
|
||||
|
||||
// Toujours valider avant soumission (sécurité supplémentaire)
|
||||
document
|
||||
.getElementById("form-import-historique")
|
||||
.addEventListener("submit", (e) => {
|
||||
const isinOk =
|
||||
document.getElementById("input-company-name").value !== "";
|
||||
if (!isinOk) {
|
||||
e.preventDefault();
|
||||
alert("Veuillez vérifier l'ISIN avant d'importer.");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -71,7 +71,7 @@ header_title %}Tableau de bord{% endblock %} {% block content %}
|
||||
|
||||
<!-- btn 8 -->
|
||||
<a
|
||||
href="#"
|
||||
href="{{ url_for('main.gestion_import_historique_csv') }}"
|
||||
class="py-6 px-6 bg-gray-950 hover:bg-blue-600/30 border-2 border-gray-800 hover:border-blue-500 rounded-xl text-white text-lg font-bold tracking-wide transition-all duration-200 flex items-center justify-center shadow-lg hover:scale-[1.01]"
|
||||
>
|
||||
8. Import historique CSV
|
||||
|
||||
Reference in New Issue
Block a user