Enhance UptimeStatusIsland component with badge toggle functionality and click handling

- Added state management for open badges and refs for badge popups.
- Implemented toggle functionality for badge information on click.
- Updated badge rendering to support mobile interactions, improving user experience.
This commit is contained in:
becarta
2025-06-08 01:55:33 +02:00
parent 0b3aea9f87
commit 8e0e26c50b

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { FiAward, FiPercent, FiActivity } from 'react-icons/fi';
function getStatusColor(validCert) {
@@ -47,6 +47,10 @@ export default function UptimeStatusIsland() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Track open badge for each monitor by monitor.id and badge type
const [openBadge, setOpenBadge] = useState({});
// Refs for each badge popup
const badgeRefs = useRef({});
const fetchData = useCallback(async () => {
setLoading(true);
@@ -67,6 +71,41 @@ export default function UptimeStatusIsland() {
fetchData();
}, [fetchData]);
// Helper to toggle badge info
const toggleBadge = (monitorId, badge) => {
setOpenBadge((prev) => ({
...prev,
[monitorId]: {
...prev[monitorId],
[badge]: !prev[monitorId]?.[badge],
},
}));
};
// Close popup on outside click
useEffect(() => {
function handleClick(e) {
let clickedInside = false;
Object.values(badgeRefs.current).forEach((monitorBadges) => {
Object.values(monitorBadges || {}).forEach((ref) => {
if (ref && ref.current && ref.current.contains(e.target)) {
clickedInside = true;
}
});
});
if (!clickedInside) setOpenBadge({});
}
document.addEventListener('mousedown', handleClick);
return () => document.removeEventListener('mousedown', handleClick);
}, []);
// Helper to get/create refs for each badge
const getBadgeRef = (monitorId, badge) => {
if (!badgeRefs.current[monitorId]) badgeRefs.current[monitorId] = {};
if (!badgeRefs.current[monitorId][badge]) badgeRefs.current[monitorId][badge] = React.createRef();
return badgeRefs.current[monitorId][badge];
};
return (
<div>
<div className="grid gap-10 md:grid-cols-2">
@@ -89,7 +128,7 @@ export default function UptimeStatusIsland() {
<span className="font-semibold text-lg text-gray-800 dark:text-gray-100 truncate">{monitor.name}</span>
</div>
{/* Cert Exp (center-left, col-span-1) */}
<div className="flex justify-center col-span-1">
<div className="flex flex-col items-center col-span-1 relative">
{monitor.certExpiryDaysRemaining !== undefined && (
<>
{/* Desktop: full badge */}
@@ -97,40 +136,72 @@ export default function UptimeStatusIsland() {
title={monitor.certExpiryDaysRemaining < 0 ? 'Certificate expired!' : `Certificate expires in ${monitor.certExpiryDaysRemaining} days`}>
{getCertText(monitor.certExpiryDaysRemaining)}
</span>
{/* Mobile: icon badge */}
<span className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getCertBg(monitor.certExpiryDaysRemaining)}`}
title={monitor.certExpiryDaysRemaining < 0 ? 'Certificate expired!' : `Certificate expires in ${monitor.certExpiryDaysRemaining} days`}>
{/* Mobile: icon badge with click */}
<span
ref={getBadgeRef(monitor.id, 'cert')}
className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getCertBg(monitor.certExpiryDaysRemaining)}`}
title={monitor.certExpiryDaysRemaining < 0 ? 'Certificate expired!' : `Certificate expires in ${monitor.certExpiryDaysRemaining} days`}
onClick={() => toggleBadge(monitor.id, 'cert')}
style={{ cursor: 'pointer', position: 'relative' }}
>
<FiAward className="w-4 h-4" />
{openBadge[monitor.id]?.cert && (
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-20 bg-white dark:bg-slate-800 text-gray-900 dark:text-gray-100 rounded-lg shadow-lg px-3 py-2 text-xs whitespace-nowrap border border-gray-200 dark:border-gray-700">
{monitor.certExpiryDaysRemaining < 0
? 'Expired!'
: `Expires in ${monitor.certExpiryDaysRemaining} days`}
</span>
)}
</span>
</>
)}
</div>
{/* Avg. (center-right, col-span-1) */}
<div className="flex justify-center col-span-1">
<div className="flex flex-col items-center col-span-1 relative">
{monitor.avgPing !== undefined && (
<>
{/* Desktop: full badge */}
<span className={`hidden sm:inline px-2 py-0 rounded-full text-xs font-medium ${getAvgPingBg(monitor.avgPing)}`}>
Avg.: {monitor.avgPing < 100 ? monitor.avgPing.toFixed(1) : Math.round(monitor.avgPing)} ms
</span>
{/* Mobile: icon badge */}
<span className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getAvgPingBg(monitor.avgPing)}`}>
{/* Mobile: icon badge with click */}
<span
ref={getBadgeRef(monitor.id, 'avg')}
className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getAvgPingBg(monitor.avgPing)}`}
onClick={() => toggleBadge(monitor.id, 'avg')}
style={{ cursor: 'pointer', position: 'relative' }}
>
<FiActivity className="w-4 h-4" />
{openBadge[monitor.id]?.avg && (
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-20 bg-white dark:bg-slate-800 text-gray-900 dark:text-gray-100 rounded-lg shadow-lg px-3 py-2 text-xs whitespace-nowrap border border-gray-200 dark:border-gray-700">
{monitor.avgPing < 100 ? monitor.avgPing.toFixed(1) : Math.round(monitor.avgPing)} ms
</span>
)}
</span>
</>
)}
</div>
{/* 24h (right, col-span-1) */}
<div className="flex justify-end col-span-1">
<div className="flex flex-col items-center col-span-1 relative">
{monitor.uptime24h !== undefined && (
<>
{/* Desktop: full badge */}
<span className={`hidden sm:inline px-2 py-0 rounded-full text-xs font-medium ${getUptime24hBg(monitor.uptime24h)}`}>
24h: {monitor.uptime24h.toFixed(1)}%
</span>
{/* Mobile: icon badge */}
<span className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getUptime24hBg(monitor.uptime24h)}`}>
{/* Mobile: icon badge with click */}
<span
ref={getBadgeRef(monitor.id, 'uptime')}
className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getUptime24hBg(monitor.uptime24h)}`}
onClick={() => toggleBadge(monitor.id, 'uptime')}
style={{ cursor: 'pointer', position: 'relative' }}
>
<FiPercent className="w-4 h-4" />
{openBadge[monitor.id]?.uptime && (
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-20 bg-white dark:bg-slate-800 text-gray-900 dark:text-gray-100 rounded-lg shadow-lg px-3 py-2 text-xs whitespace-nowrap border border-gray-200 dark:border-gray-700">
{monitor.uptime24h.toFixed(1)}%
</span>
)}
</span>
</>
)}