suite des maj
This commit is contained in:
@ -469,6 +469,9 @@ export function IncidentsView() {
|
||||
</div>
|
||||
</div>
|
||||
</div>{/* end grid */}
|
||||
<div className="mt-6">
|
||||
<MiniHeatmap />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -500,3 +503,57 @@ function MetricCard({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Mini Heatmap ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface HeatmapHour {
|
||||
hour: number;
|
||||
hits: number;
|
||||
unique_ips: number;
|
||||
}
|
||||
|
||||
function MiniHeatmap() {
|
||||
const [data, setData] = useState<HeatmapHour[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/heatmap/hourly')
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(d => { if (d) setData(d.hours ?? d.items ?? []); })
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
if (data.length === 0) return null;
|
||||
|
||||
const maxHits = Math.max(...data.map(d => d.hits), 1);
|
||||
|
||||
const barColor = (hits: number) => {
|
||||
const pct = (hits / maxHits) * 100;
|
||||
if (pct >= 75) return 'bg-red-500/70';
|
||||
if (pct >= 50) return 'bg-purple-500/60';
|
||||
if (pct >= 25) return 'bg-blue-500/50';
|
||||
if (pct >= 5) return 'bg-blue-400/30';
|
||||
return 'bg-slate-700/30';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-background-secondary border border-border rounded-lg p-4">
|
||||
<div className="text-sm font-semibold text-text-primary mb-3">⏱️ Activité par heure (72h)</div>
|
||||
<div className="flex items-end gap-px h-16">
|
||||
{data.map((d, i) => (
|
||||
<div key={i} className="relative flex-1 flex flex-col items-center justify-end group">
|
||||
<div
|
||||
className={`w-full rounded-sm ${barColor(d.hits)}`}
|
||||
style={{ height: `${Math.max((d.hits / maxHits) * 100, 2)}%` }}
|
||||
/>
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 hidden group-hover:flex bg-background-card border border-border text-xs text-text-primary rounded px-2 py-1 whitespace-nowrap z-10 pointer-events-none">
|
||||
{d.hits.toLocaleString()} hits — {d.unique_ips} IPs
|
||||
</div>
|
||||
<div className="text-[9px] text-text-disabled mt-0.5 leading-none">
|
||||
{[0, 6, 12, 18].includes(d.hour) ? `${d.hour}h` : '\u00a0'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user