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';
|
import { FiAward, FiPercent, FiActivity } from 'react-icons/fi';
|
||||||
|
|
||||||
function getStatusColor(validCert) {
|
function getStatusColor(validCert) {
|
||||||
@@ -47,6 +47,10 @@ export default function UptimeStatusIsland() {
|
|||||||
const [data, setData] = useState(null);
|
const [data, setData] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
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 () => {
|
const fetchData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -67,6 +71,41 @@ export default function UptimeStatusIsland() {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, [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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="grid gap-10 md:grid-cols-2">
|
<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>
|
<span className="font-semibold text-lg text-gray-800 dark:text-gray-100 truncate">{monitor.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Cert Exp (center-left, col-span-1) */}
|
{/* 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 && (
|
{monitor.certExpiryDaysRemaining !== undefined && (
|
||||||
<>
|
<>
|
||||||
{/* Desktop: full badge */}
|
{/* Desktop: full badge */}
|
||||||
@@ -97,40 +136,72 @@ export default function UptimeStatusIsland() {
|
|||||||
title={monitor.certExpiryDaysRemaining < 0 ? 'Certificate expired!' : `Certificate expires in ${monitor.certExpiryDaysRemaining} days`}>
|
title={monitor.certExpiryDaysRemaining < 0 ? 'Certificate expired!' : `Certificate expires in ${monitor.certExpiryDaysRemaining} days`}>
|
||||||
{getCertText(monitor.certExpiryDaysRemaining)}
|
{getCertText(monitor.certExpiryDaysRemaining)}
|
||||||
</span>
|
</span>
|
||||||
{/* Mobile: icon badge */}
|
{/* Mobile: icon badge with click */}
|
||||||
<span className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getCertBg(monitor.certExpiryDaysRemaining)}`}
|
<span
|
||||||
title={monitor.certExpiryDaysRemaining < 0 ? 'Certificate expired!' : `Certificate expires in ${monitor.certExpiryDaysRemaining} days`}>
|
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" />
|
<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>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Avg. (center-right, col-span-1) */}
|
{/* 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 && (
|
{monitor.avgPing !== undefined && (
|
||||||
<>
|
<>
|
||||||
{/* Desktop: full badge */}
|
{/* Desktop: full badge */}
|
||||||
<span className={`hidden sm:inline px-2 py-0 rounded-full text-xs font-medium ${getAvgPingBg(monitor.avgPing)}`}>
|
<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
|
Avg.: {monitor.avgPing < 100 ? monitor.avgPing.toFixed(1) : Math.round(monitor.avgPing)} ms
|
||||||
</span>
|
</span>
|
||||||
{/* Mobile: icon badge */}
|
{/* Mobile: icon badge with click */}
|
||||||
<span className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getAvgPingBg(monitor.avgPing)}`}>
|
<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" />
|
<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>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 24h (right, col-span-1) */}
|
{/* 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 && (
|
{monitor.uptime24h !== undefined && (
|
||||||
<>
|
<>
|
||||||
{/* Desktop: full badge */}
|
{/* Desktop: full badge */}
|
||||||
<span className={`hidden sm:inline px-2 py-0 rounded-full text-xs font-medium ${getUptime24hBg(monitor.uptime24h)}`}>
|
<span className={`hidden sm:inline px-2 py-0 rounded-full text-xs font-medium ${getUptime24hBg(monitor.uptime24h)}`}>
|
||||||
24h: {monitor.uptime24h.toFixed(1)}%
|
24h: {monitor.uptime24h.toFixed(1)}%
|
||||||
</span>
|
</span>
|
||||||
{/* Mobile: icon badge */}
|
{/* Mobile: icon badge with click */}
|
||||||
<span className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getUptime24hBg(monitor.uptime24h)}`}>
|
<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" />
|
<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>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
Reference in New Issue
Block a user