Files
Front-DataSentinel/DataSentinel/src/pages/MonitoringDetail/MonitoringDetail.jsx
T
Antho 8b8121ca1c refactor(front): centralise la règle de statut et les colonnes dupliquées
Le seuil de 10 erreurs qui fait basculer un monitoring en « Critique »
était réécrit dans trois fichiers (MonitoringCard, MonitoringDetail,
VueConso), la version de VueConso ne renvoyant qu'une couleur. Faire
évoluer ce seuil imposait de corriger trois endroits sans en oublier.
Il vit maintenant dans utils/statutMonitoring.js, avec la constante
SEUIL_ATTENTION nommée.

Les colonnes du tableau de monitorings étaient également identiques
entre VueConso et ServiceMonitorings : extraites dans
utils/monitoringColumns.jsx.
2026-08-15 14:29:09 +02:00

143 lines
5.4 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// src/pages/MonitoringDetail/MonitoringDetail.jsx
// Page de détail d'un monitoring avec fil d'Ariane, tableau d'erreurs, export CSV
import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Download, TrendingUp } from 'lucide-react';
import { getMonitoringById, getMonitoringDetails, getMonitoringCount, getMonitoringColumns, exportToCSV } from '../../services/api';
import DataTable from '../../components/common/DataTable';
import LoadingSpinner from '../../components/common/LoadingSpinner';
import ErrorMessage from '../../components/common/ErrorMessage';
import Badge from '../../components/common/Badge';
import Button from '../../components/common/Button';
import { getStatutMonitoring } from '../../utils/statutMonitoring';
import styles from './MonitoringDetail.module.css';
export default function MonitoringDetail() {
const { id } = useParams();
const navigate = useNavigate();
const [monitoring, setMonitoring] = useState(null);
const [details, setDetails] = useState([]);
const [columns, setColumns] = useState([]);
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(true);
const [erreur, setErreur] = useState('');
const [search, setSearch] = useState('');
useEffect(() => {
async function loadData() {
setLoading(true);
setErreur('');
try {
const [monit, det, cnt, cols] = await Promise.all([
getMonitoringById(id),
getMonitoringDetails(id, { limit: 5000 }), // Charge maximum pour export complet
getMonitoringCount(id),
getMonitoringColumns(id)
]);
setMonitoring(monit);
setDetails(Array.isArray(det) ? det : []);
setCount(cnt.count ?? cnt.nb_erreurs ?? 0);
// Traite les colonnes de l'API
if (cols?.columns) {
const colsList = cols.columns.map(col => ({
key: col.COLUMN_NAME,
label: col.COLUMN_NAME
}));
setColumns(colsList);
}
} catch (e) {
setErreur(e.message || 'Erreur lors du chargement du monitoring.');
} finally {
setLoading(false);
}
}
loadData();
}, [id]);
/* Filtrage des détails par recherche */
const filteredDetails = details.filter(row => {
if (!search) return true;
return Object.values(row).some(val =>
String(val || '').toLowerCase().includes(search.toLowerCase())
);
});
if (loading) return <LoadingSpinner message="Chargement du monitoring..." />;
if (erreur) return <ErrorMessage message={erreur} />;
const statut = getStatutMonitoring(count);
return (
<div className={styles.page}>
{/* Fil d'Ariane */}
<div className={styles.breadcrumb}>
<span onClick={() => navigate('/dashboard')} className={styles.breadcrumbLink}>Dashboard</span>
<span className={styles.separator}></span>
<span>{monitoring?.service || 'Service'}</span>
<span className={styles.separator}></span>
<span>{monitoring?.nom || 'Monitoring'}</span>
</div>
{/* En-tête */}
<div className={styles.header}>
<div className={styles.titleSection}>
<h1 className={styles.title}>{monitoring?.nom || monitoring?.monito_intitule || 'Monitoring'}</h1>
<div className={styles.badges}>
{monitoring?.bdd_source && <Badge variant={monitoring.bdd_source.toLowerCase() === 'sage' ? 'sage' : 'crm'}>{monitoring.bdd_source}</Badge>}
{monitoring?.id_categorie && <Badge variant="info">Catégorie {monitoring.id_categorie}</Badge>}
<Badge variant={statut.variant}>{statut.label}</Badge>
</div>
</div>
<div className={styles.count}>
<span className={styles.countNumber}>{count}</span>
<span className={styles.countLabel}>erreur{count !== 1 ? 's' : ''}</span>
</div>
</div>
{/* Actions */}
<div className={styles.actions}>
<Button variant="secondary" onClick={() => exportToCSV(filteredDetails, `monitoring_${id}_${new Date().toISOString().split('T')[0]}.csv`)}>
<Download size={14} />
Exporter CSV ({filteredDetails.length} lignes)
</Button>
<Button variant="primary" onClick={() => navigate(`/historique?id=${id}`)}>
<TrendingUp size={14} />
Voir l'évolution
</Button>
</div>
{/* Filtres */}
<div className={styles.filtres}>
<input
type="text"
placeholder="Rechercher dans les erreurs..."
className={styles.searchInput}
value={search}
onChange={e => setSearch(e.target.value)}
/>
</div>
{/* Résumé des résultats */}
<div style={{ marginBottom: '1rem', fontSize: '0.9rem', color: 'var(--color-text-secondary)' }}>
{filteredDetails.length} / {details.length} erreur{details.length !== 1 ? 's' : ''}
{search && ` (filtré: ${search})`}
</div>
{/* Tableau */}
{columns.length > 0 && (
<DataTable
columns={columns}
data={filteredDetails}
pageSize={50}
emptyMessage="Aucune erreur trouvée."
/>
)}
{/* Encart aide */}
<div className={styles.helpBox}>
<p><strong>Aide :</strong> Ce tableau affiche les erreurs détectées pour ce monitoring. Utilisez la barre de recherche pour filtrer. L'export CSV permet de télécharger toutes les données filtrées.</p>
</div>
</div>
);
}