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.
143 lines
5.4 KiB
React
143 lines
5.4 KiB
React
// 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>
|
||
);
|
||
} |