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:
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
Reference in New Issue
Block a user