From c44c4ba8c4ec3c1921aa72d65d9da9e974c188b4 Mon Sep 17 00:00:00 2001 From: gearnode Date: Wed, 19 Feb 2025 15:49:02 +0100 Subject: [PATCH] Add control tooltip Signed-off-by: gearnode --- .../src/pages/FrameworkOverviewPage.tsx | 216 +++++++++++++----- 1 file changed, 158 insertions(+), 58 deletions(-) diff --git a/apps/console/src/pages/FrameworkOverviewPage.tsx b/apps/console/src/pages/FrameworkOverviewPage.tsx index b2c1d6767..52fa451e8 100644 --- a/apps/console/src/pages/FrameworkOverviewPage.tsx +++ b/apps/console/src/pages/FrameworkOverviewPage.tsx @@ -1,4 +1,4 @@ -import { Suspense, useEffect } from "react"; +import { Suspense, useEffect, useState } from "react"; import { useParams } from "react-router"; import { graphql, @@ -6,13 +6,14 @@ import { usePreloadedQuery, useQueryLoader, } from "react-relay"; -import { Shield, FileText, Clock } from "lucide-react"; +import { Shield, FileText, Clock, MoveUpRight } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import type { FrameworkOverviewPageQuery as FrameworkOverviewPageQueryType } from "./__generated__/FrameworkOverviewPageQuery.graphql"; import { Helmet } from "react-helmet-async"; import { useBreadcrumb } from "@/contexts/BreadcrumbContext"; +import { createPortal } from "react-dom"; const FrameworkOverviewPageQuery = graphql` query FrameworkOverviewPageQuery($frameworkId: ID!) { @@ -46,6 +47,14 @@ function FrameworkOverviewPageContent({ const { setBreadcrumbSegment } = useBreadcrumb(); const framework = data.node; const controls = framework.controls?.edges.map((edge) => edge?.node) ?? []; + const [hoveredCard, setHoveredCard] = useState(null); + const [hoveredControl, setHoveredControl] = useState(null); + const [tooltipPosition, setTooltipPosition] = useState<{ + x: number; + y: number; + width: number; + } | null>(null); + const [isTooltipHovered, setIsTooltipHovered] = useState(false); useEffect(() => { if (framework?.name) { @@ -91,43 +100,45 @@ function FrameworkOverviewPageContent({

Timeline

-
+
12 hours left
-
-

Preparation phase

-
-
-
-
+
+
+

Preparation phase

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Preparation -
-
- Observation period: 3 month -
-
- Audit: 6-9 days -
-
- Report: 10-14 days +
+
+ Preparation +
+
+ Observation period: 3 month +
+
+ Audit: 6-9 days +
+
+ Report: 10-14 days +
@@ -145,37 +156,126 @@ function FrameworkOverviewPageContent({ {controlCards.map((card, index) => (
-
-
- - {card.title} +
+
+
+ + {card.title} +
+ + U +
- - U - -
-
-
- {Array(card.total) - .fill(0) - .map((_, i) => ( -
- ))} +
+
+ {Array(card.total) + .fill(0) + .map((_, i) => { + const control = card.controls[i]; + return ( +
{ + setHoveredCard(index); + setHoveredControl(i); + const rect = + e.currentTarget.getBoundingClientRect(); + + setTooltipPosition({ + x: rect.left, + y: rect.top - 10, + width: 400, + }); + }} + onMouseLeave={() => { + setTimeout(() => { + if (!isTooltipHovered) { + setHoveredCard(null); + setHoveredControl(null); + setTooltipPosition(null); + } + }, 500); + }} + title={control?.name} + /> + ); + })} +
+ + {card.completed}/{card.total} Controls validated +
- - {card.completed}/{card.total} Controls validated -
))}
+ + {hoveredCard !== null && + hoveredControl !== null && + tooltipPosition && + createPortal( +
setIsTooltipHovered(true)} + onMouseLeave={() => { + setIsTooltipHovered(false); + setHoveredCard(null); + setHoveredControl(null); + setTooltipPosition(null); + }} + style={{ + left: tooltipPosition.x, + transform: `translate(-50%, -100%)`, + top: tooltipPosition.y - 20, + width: tooltipPosition.width + "px", + }} + > +
+
+
+
+ 30 min +
+
+ Mandatory +
+
+ Assigned to you +
+
+ +
+
+ {controlCards[hoveredCard]?.controls[hoveredControl]?.name} +
+
+ { + controlCards[hoveredCard]?.controls[hoveredControl] + ?.description + } +
+
+
+
+
+ + {controlCards[hoveredCard]?.controls[hoveredControl] + ?.state === "IMPLEMENTED" + ? "Validated" + : "Not validated"} + +
+
+
, + document.body, + )}
); }