From 7c8c54eb34cba9791de1799ae18a6cea4eff4276 Mon Sep 17 00:00:00 2001 From: Yannick Carissan Date: Mon, 28 Sep 2026 10:36:25 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20tableau=20interactif=20'O=C3=B9=20faire?= =?UTF-8?q?=20quoi'=20avec=20recherche=20et=20tri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout composant SearchableSortableTable.jsx pour les tableaux interactifs - Nouvelle page docs/acces/ou-faire-quoi.mdx avec tableau 3 colonnes - Mise à jour sidebar_position pour cours.md (2), ssh.md (4), projets.md (5) - Intégration dans le menu 'Le portail MesoNET' Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- docs/acces/courses.md | 5 + docs/acces/ou-faire-quoi.mdx | 50 +++++++ docs/acces/projets.md | 2 +- docs/acces/ssh.md | 2 +- src/components/SearchableSortableTable.jsx | 157 +++++++++++++++++++++ 5 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 docs/acces/ou-faire-quoi.mdx create mode 100644 src/components/SearchableSortableTable.jsx diff --git a/docs/acces/courses.md b/docs/acces/courses.md index b2809d783..9da54f19c 100644 --- a/docs/acces/courses.md +++ b/docs/acces/courses.md @@ -1,3 +1,8 @@ +--- +title: "Pour les formations" +sidebar_position: 2 +--- + # Pour les formations (page en cours de création) diff --git a/docs/acces/ou-faire-quoi.mdx b/docs/acces/ou-faire-quoi.mdx new file mode 100644 index 000000000..79a89e035 --- /dev/null +++ b/docs/acces/ou-faire-quoi.mdx @@ -0,0 +1,50 @@ +--- +title: "Où faire quoi" +sidebar_position: 3 +--- + +import SearchableSortableTable from '@site/src/components/SearchableSortableTable'; + +# Où faire quoi + +Ce tableau vous permet de trouver la machine MesoNET la plus adaptée à vos besoins. + + diff --git a/docs/acces/projets.md b/docs/acces/projets.md index 7e3f9d82c..77ab983b1 100644 --- a/docs/acces/projets.md +++ b/docs/acces/projets.md @@ -1,6 +1,6 @@ --- title: "Créer et gérer des projets" -sidebar_position: 3 +sidebar_position: 5 --- :::info diff --git a/docs/acces/ssh.md b/docs/acces/ssh.md index 5d212f626..c8c73f84b 100644 --- a/docs/acces/ssh.md +++ b/docs/acces/ssh.md @@ -1,6 +1,6 @@ --- title: "Gérer ses clés SSH" -sidebar_position: 2 +sidebar_position: 4 --- # Gérer ses clés SSH diff --git a/src/components/SearchableSortableTable.jsx b/src/components/SearchableSortableTable.jsx new file mode 100644 index 000000000..61b7548b9 --- /dev/null +++ b/src/components/SearchableSortableTable.jsx @@ -0,0 +1,157 @@ +import React, { useState, useMemo } from 'react'; + +/** + * Composant de tableau avec recherche et tri + * @param {Object} props + * @param {Array} props.data - Tableau d'objets représentant les lignes + * @param {Array} props.columns - Tableau d'objets { key, label } pour les colonnes + * @param {string} props.title - Titre du tableau (optionnel) + */ +const SearchableSortableTable = ({ data, columns, title }) => { + const [searchTerm, setSearchTerm] = useState(''); + const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); + + // Filtrer les données en fonction du terme de recherche + const filteredData = useMemo(() => { + if (!searchTerm) return data; + + const lowerSearchTerm = searchTerm.toLowerCase(); + return data.filter(row => + columns.some(col => { + const value = row[col.key]; + return value && value.toString().toLowerCase().includes(lowerSearchTerm); + }) + ); + }, [data, searchTerm, columns]); + + // Trier les données + const sortedData = useMemo(() => { + if (!sortConfig.key) return filteredData; + + return [...filteredData].sort((a, b) => { + const aValue = a[sortConfig.key] || ''; + const bValue = b[sortConfig.key] || ''; + + if (aValue < bValue) { + return sortConfig.direction === 'asc' ? -1 : 1; + } + if (aValue > bValue) { + return sortConfig.direction === 'asc' ? 1 : -1; + } + return 0; + }); + }, [filteredData, sortConfig]); + + // Gérer le clic sur l'en-tête de colonne pour le tri + const requestSort = (key) => { + let direction = 'asc'; + if (sortConfig.key === key && sortConfig.direction === 'asc') { + direction = 'desc'; + } + setSortConfig({ key, direction }); + }; + + // Obtenir l'icône de tri pour une colonne + const getSortIcon = (key) => { + if (sortConfig.key !== key) return '↕'; + return sortConfig.direction === 'asc' ? '↑' : '↓'; + }; + + return ( +
+ {title && ( +

{title}

+ )} + + {/* Barre de recherche */} +
+ setSearchTerm(e.target.value)} + style={{ + padding: '8px 12px', + border: '1px solid #e0e0e0', + borderRadius: '4px', + width: '100%', + maxWidth: '400px', + fontSize: '14px', + }} + /> +
+ + {/* Tableau */} +
+ + + + {columns.map((col) => ( + + ))} + + + + {sortedData.length > 0 ? ( + sortedData.map((row, index) => ( + + {columns.map((col) => ( + + ))} + + )) + ) : ( + + + + )} + +
requestSort(col.key)} + style={{ + padding: '12px', + textAlign: 'left', + borderBottom: '2px solid #e0e0e0', + cursor: 'pointer', + userSelect: 'none', + }} + > +
+ {col.label} + {getSortIcon(col.key)} +
+
requestSort(col.key)} + > + {row[col.key]} +
+ Aucun résultat trouvé +
+
+ + {/* Afficher le nombre de résultats */} +
+ {sortedData.length} résultat(s) sur {data.length} au total +
+
+ ); +}; + +export default SearchableSortableTable;