2026-07-23 09:56:47 +02:00
|
|
|
{% extends "base.html" %} {% block title %}Gestion des actions - Bolsa{%
|
|
|
|
|
endblock %} {% block header_title %}Gestion actions{% endblock %} {% block
|
|
|
|
|
content %}
|
|
|
|
|
<div class="flex flex-col items-center justify-center py-6 relative">
|
2026-07-24 14:38:35 +02:00
|
|
|
<!-- Système de Toasts -->
|
2026-07-23 09:56:47 +02:00
|
|
|
<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 flex items-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 {% 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-6xl"
|
|
|
|
|
>
|
|
|
|
|
<!-- En-tête de section -->
|
|
|
|
|
<div
|
|
|
|
|
class="flex justify-between items-center mb-6 border-b border-gray-800 pb-4"
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="text-2xl font-extrabold text-white">
|
|
|
|
|
Administration des actions
|
|
|
|
|
</h2>
|
|
|
|
|
<p class="text-gray-400 text-sm">
|
|
|
|
|
Gérez le référentiel des actions et valeurs boursières.
|
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
<!-- Grille principale -->
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-8 py-4">
|
|
|
|
|
<!-- COLONNE GAUCHE : Recherche et Liste -->
|
|
|
|
|
<div
|
2026-07-24 14:38:35 +02:00
|
|
|
class="md:col-span-4 bg-gray-950 border border-gray-800 rounded-xl p-4 flex flex-col h-[580px]"
|
2026-07-23 09:56:47 +02:00
|
|
|
>
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
2026-07-24 14:38:35 +02:00
|
|
|
>Rechercher (ISIN, Ticker, Nom, Pays)</label
|
2026-07-23 09:56:47 +02:00
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="search-input"
|
|
|
|
|
oninput="filtrerActions()"
|
|
|
|
|
placeholder="Filtrer..."
|
|
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<h3
|
|
|
|
|
class="text-sm font-semibold text-gray-300 uppercase tracking-wider mb-2 px-1"
|
|
|
|
|
>
|
|
|
|
|
Actions enregistrées
|
|
|
|
|
</h3>
|
|
|
|
|
|
2026-07-24 14:38:35 +02:00
|
|
|
<!-- Liste avec délégation d'événements (Nom et Ticker uniquement) -->
|
2026-07-23 09:56:47 +02:00
|
|
|
<div
|
|
|
|
|
class="overflow-y-auto flex-1 space-y-1 pr-1"
|
|
|
|
|
id="actions-list-container"
|
|
|
|
|
>
|
|
|
|
|
{% for a in actions %}
|
|
|
|
|
<div
|
2026-07-24 14:38:35 +02:00
|
|
|
class="action-item cursor-pointer px-3 py-2.5 rounded-lg {% if loop.first %}bg-blue-600 text-white{% else %}hover:bg-gray-800 text-gray-300{% endif %} font-medium text-sm transition flex justify-between items-center"
|
2026-07-23 09:56:47 +02:00
|
|
|
data-id="{{ a.id }}"
|
|
|
|
|
data-isin="{{ a.isin }}"
|
2026-07-24 14:38:35 +02:00
|
|
|
data-ticker="{{ a.ticker }}"
|
|
|
|
|
data-company="{{ a.company_name }}"
|
|
|
|
|
data-exchange="{{ a.exchange }}"
|
|
|
|
|
data-pays="{{ a.pays }}"
|
|
|
|
|
data-currency="{{ a.currency }}"
|
|
|
|
|
data-updated="{{ a.updated_at }}"
|
|
|
|
|
data-search="{{ a.isin | lower }} {{ a.ticker | lower }} {{ a.company_name | lower }} {{ a.exchange | lower }} {{ a.pays | lower }}"
|
2026-07-23 09:56:47 +02:00
|
|
|
>
|
2026-07-24 14:38:35 +02:00
|
|
|
<span class="truncate font-semibold pr-2"
|
|
|
|
|
>{{ a.company_name }}</span
|
|
|
|
|
>
|
|
|
|
|
<span
|
|
|
|
|
class="ticker-badge text-xs {% if loop.first %}bg-blue-800 text-white{% else %}bg-gray-800 text-gray-400{% endif %} px-2 py-0.5 rounded whitespace-nowrap font-mono"
|
2026-07-23 09:56:47 +02:00
|
|
|
>
|
2026-07-24 14:38:35 +02:00
|
|
|
{{ a.ticker or 'N/A' }}
|
|
|
|
|
</span>
|
2026-07-23 09:56:47 +02:00
|
|
|
</div>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- COLONNE DROITE : Formulaire -->
|
|
|
|
|
<div
|
|
|
|
|
class="md:col-span-8 bg-gray-950 border border-gray-800 rounded-xl p-6 flex flex-col justify-between"
|
|
|
|
|
>
|
|
|
|
|
<form
|
|
|
|
|
id="action-form"
|
|
|
|
|
method="POST"
|
|
|
|
|
action=""
|
|
|
|
|
class="space-y-4 flex-1 flex flex-col justify-between"
|
|
|
|
|
>
|
|
|
|
|
<input type="hidden" id="field-id" name="id" />
|
|
|
|
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
<h3
|
|
|
|
|
class="text-sm font-semibold text-gray-300 uppercase tracking-wider mb-2"
|
|
|
|
|
>
|
|
|
|
|
Détails de l'action
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
|
|
|
|
>ISIN</label
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="field-isin"
|
|
|
|
|
name="isin"
|
|
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
|
|
|
|
>Ticker</label
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="field-ticker"
|
|
|
|
|
name="ticker"
|
|
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
2026-07-24 14:38:35 +02:00
|
|
|
>Nom de la société (company_name)</label
|
2026-07-23 09:56:47 +02:00
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
2026-07-24 14:38:35 +02:00
|
|
|
id="field-company"
|
|
|
|
|
name="company_name"
|
2026-07-23 09:56:47 +02:00
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
2026-07-24 14:38:35 +02:00
|
|
|
>Exchange</label
|
2026-07-23 09:56:47 +02:00
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
2026-07-24 14:38:35 +02:00
|
|
|
id="field-exchange"
|
|
|
|
|
name="exchange"
|
2026-07-23 09:56:47 +02:00
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-07-24 14:38:35 +02:00
|
|
|
<div class="grid grid-cols-3 gap-4">
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
|
|
|
|
>Pays</label
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="field-pays"
|
|
|
|
|
name="pays"
|
|
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-07-23 09:56:47 +02:00
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
2026-07-24 14:38:35 +02:00
|
|
|
>Devise (currency)</label
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="field-currency"
|
|
|
|
|
name="currency"
|
|
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-400 mb-1"
|
|
|
|
|
>Mise à jour</label
|
2026-07-23 09:56:47 +02:00
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="field-updated_at"
|
|
|
|
|
name="updated_at"
|
|
|
|
|
readonly
|
|
|
|
|
class="w-full bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-gray-400 text-sm cursor-not-allowed"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
class="flex items-center justify-end space-x-3 pt-6 border-t border-gray-800 mt-6"
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onclick="preparerCreation()"
|
|
|
|
|
class="px-4 py-2 bg-gray-800 hover:bg-gray-700 text-gray-300 text-sm font-medium rounded-lg transition duration-200"
|
|
|
|
|
>
|
|
|
|
|
Nouveau / Vider
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onclick="annulerAction()"
|
|
|
|
|
class="px-4 py-2 bg-gray-800 hover:bg-gray-700 text-gray-300 text-sm font-medium rounded-lg transition duration-200"
|
|
|
|
|
>
|
|
|
|
|
Annuler
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
name="action"
|
|
|
|
|
value="creer"
|
|
|
|
|
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-medium rounded-lg transition duration-200"
|
|
|
|
|
>
|
|
|
|
|
Créer
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
name="action"
|
|
|
|
|
value="modifier"
|
|
|
|
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium rounded-lg transition duration-200"
|
|
|
|
|
>
|
|
|
|
|
Modifier
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
class="border-t border-gray-800 pt-4 text-xs text-gray-400 italic text-center"
|
|
|
|
|
>
|
|
|
|
|
Positions en bourse : Accès sécurisé. Veillez à fermer votre session après
|
|
|
|
|
chaque utilisation.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-24 14:38:35 +02:00
|
|
|
const container = document.getElementById("actions-list-container");
|
|
|
|
|
if (container) {
|
|
|
|
|
container.addEventListener("click", (e) => {
|
|
|
|
|
const item = e.target.closest(".action-item");
|
|
|
|
|
if (item) {
|
|
|
|
|
selectionnerActionItem(item);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 09:56:47 +02:00
|
|
|
const premiereAction = document.querySelector(".action-item");
|
|
|
|
|
if (premiereAction) {
|
2026-07-24 14:38:35 +02:00
|
|
|
selectionnerActionItem(premiereAction);
|
2026-07-23 09:56:47 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-24 14:38:35 +02:00
|
|
|
function selectionnerActionItem(item) {
|
|
|
|
|
document.getElementById("field-id").value = item.dataset.id;
|
|
|
|
|
document.getElementById("field-isin").value = item.dataset.isin;
|
|
|
|
|
document.getElementById("field-ticker").value = item.dataset.ticker;
|
|
|
|
|
document.getElementById("field-company").value = item.dataset.company;
|
|
|
|
|
document.getElementById("field-exchange").value = item.dataset.exchange;
|
|
|
|
|
document.getElementById("field-pays").value = item.dataset.pays;
|
|
|
|
|
document.getElementById("field-currency").value = item.dataset.currency;
|
|
|
|
|
document.getElementById("field-updated_at").value = item.dataset.updated;
|
2026-07-23 09:56:47 +02:00
|
|
|
|
|
|
|
|
document.querySelectorAll(".action-item").forEach((el) => {
|
|
|
|
|
el.classList.remove("bg-blue-600", "text-white");
|
|
|
|
|
el.classList.add("hover:bg-gray-800", "text-gray-300");
|
2026-07-24 14:38:35 +02:00
|
|
|
const badge = el.querySelector(".ticker-badge");
|
2026-07-23 09:56:47 +02:00
|
|
|
if (badge) {
|
2026-07-24 14:38:35 +02:00
|
|
|
badge.classList.remove("bg-blue-800", "text-white");
|
2026-07-23 09:56:47 +02:00
|
|
|
badge.classList.add("bg-gray-800", "text-gray-400");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-24 14:38:35 +02:00
|
|
|
item.classList.remove("hover:bg-gray-800", "text-gray-300");
|
|
|
|
|
item.classList.add("bg-blue-600", "text-white");
|
|
|
|
|
const badge = item.querySelector(".ticker-badge");
|
|
|
|
|
if (badge) {
|
|
|
|
|
badge.classList.remove("bg-gray-800", "text-gray-400");
|
|
|
|
|
badge.classList.add("bg-blue-800", "text-white");
|
2026-07-23 09:56:47 +02:00
|
|
|
}
|
2026-07-24 14:38:35 +02:00
|
|
|
item.scrollIntoView({ block: "nearest" });
|
2026-07-23 09:56:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function preparerCreation() {
|
|
|
|
|
document.getElementById("field-id").value = "";
|
|
|
|
|
document.getElementById("field-isin").value = "";
|
|
|
|
|
document.getElementById("field-ticker").value = "";
|
2026-07-24 14:38:35 +02:00
|
|
|
document.getElementById("field-company").value = "";
|
|
|
|
|
document.getElementById("field-exchange").value = "";
|
|
|
|
|
document.getElementById("field-pays").value = "";
|
|
|
|
|
document.getElementById("field-currency").value = "";
|
2026-07-23 09:56:47 +02:00
|
|
|
document.getElementById("field-updated_at").value =
|
|
|
|
|
"Automatique à la création";
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll(".action-item").forEach((el) => {
|
|
|
|
|
el.classList.remove("bg-blue-600", "text-white");
|
|
|
|
|
el.classList.add("hover:bg-gray-800", "text-gray-300");
|
2026-07-24 14:38:35 +02:00
|
|
|
const badge = el.querySelector(".ticker-badge");
|
2026-07-23 09:56:47 +02:00
|
|
|
if (badge) {
|
2026-07-24 14:38:35 +02:00
|
|
|
badge.classList.remove("bg-blue-800", "text-white");
|
2026-07-23 09:56:47 +02:00
|
|
|
badge.classList.add("bg-gray-800", "text-gray-400");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function filtrerActions() {
|
|
|
|
|
const terme = document
|
|
|
|
|
.getElementById("search-input")
|
|
|
|
|
.value.toLowerCase()
|
|
|
|
|
.trim();
|
|
|
|
|
const items = document.querySelectorAll(".action-item");
|
|
|
|
|
let premierCorrespondant = null;
|
|
|
|
|
|
|
|
|
|
items.forEach((item) => {
|
|
|
|
|
const texteRecherche = item.getAttribute("data-search");
|
|
|
|
|
if (texteRecherche.includes(terme)) {
|
|
|
|
|
item.style.display = "flex";
|
|
|
|
|
if (!premierCorrespondant) {
|
|
|
|
|
premierCorrespondant = item;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
item.style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (premierCorrespondant && terme !== "") {
|
2026-07-24 14:38:35 +02:00
|
|
|
selectionnerActionItem(premierCorrespondant);
|
2026-07-23 09:56:47 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function annulerAction() {
|
|
|
|
|
document.getElementById("search-input").value = "";
|
|
|
|
|
filtrerActions();
|
|
|
|
|
const premiereAction = document.querySelector(".action-item");
|
|
|
|
|
if (premiereAction) {
|
2026-07-24 14:38:35 +02:00
|
|
|
selectionnerActionItem(premiereAction);
|
2026-07-23 09:56:47 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|