From 148b940161f23fec9f49d596e145fce206ed2115 Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Wed, 3 Jul 2024 10:12:25 +0200
Subject: [PATCH] feat: integrate tooltipCard with schema

---
 .../lib/components/CardToolTipVis/index.tsx   |  15 +-
 .../shared/lib/components/tooltip/Tooltip.tsx | 141 +++++++++---------
 libs/shared/lib/schema/model/reactflow.tsx    |   4 +
 .../pills/nodes/entity/SchemaEntityPill.tsx   |  37 ++++-
 .../pills/nodes/entity/SchemaEntityPopup.tsx  |   2 +
 .../nodes/relation/SchemaRelationPill.tsx     |  43 +++++-
 6 files changed, 161 insertions(+), 81 deletions(-)

diff --git a/libs/shared/lib/components/CardToolTipVis/index.tsx b/libs/shared/lib/components/CardToolTipVis/index.tsx
index e6cb7aa54..7a274cdeb 100644
--- a/libs/shared/lib/components/CardToolTipVis/index.tsx
+++ b/libs/shared/lib/components/CardToolTipVis/index.tsx
@@ -32,8 +32,10 @@ export const CardToolTipVis: React.FC<CardToolTipVisProps> = ({
   numberOfElements,
 }) => {
   const itemsToShow = Object.entries(data).slice(0, maxVisibleItems);
+
+  //  shadow-md
   return (
-    <div className="border-1 border-sec-200 bg-white w-[17rem] shadow-md">
+    <div className="border-1 border-sec-200 bg-white w-[12rem]">
       <div className="flex m-0 items-center border-b border-sec-200">
         <div className="h-9 w-2" style={{ backgroundColor: colorHeader }}></div>
         <span className="text-xl ml-2 font-semibold">{name}</span>
@@ -63,11 +65,15 @@ export const CardToolTipVis: React.FC<CardToolTipVisProps> = ({
 
       <TooltipProvider delayDuration={300}>
         <div className={`px-4 py-2 ${data.length > maxVisibleItems ? 'max-h-20 overflow-y-auto' : ''}`}>
-          {data &&
+          {data && Object.keys(data).length === 0 ? (
+            <div className="flex justify-center items-center h-full">
+              <span>No attributes</span>
+            </div>
+          ) : (
             Object.entries(data).map(([k, v]) => (
               <Tooltip key={k}>
                 <div className="flex flex-row gap-3 items-center">
-                  <span className="font-semibold truncate w-[40%]">{k}</span>
+                  <span className={`font-semibold truncate ${type === 'schema' ? 'w-[90%] ' : 'w-[40%] '}    `}>{k}</span>
                   <TooltipTrigger asChild>
                     <span className="ml-auto text-right truncate w-[60%] min-h-[24px]">
                       {type === 'schema' ? (
@@ -91,7 +97,8 @@ export const CardToolTipVis: React.FC<CardToolTipVisProps> = ({
                   </TooltipContent>
                 </div>
               </Tooltip>
-            ))}
+            ))
+          )}
         </div>
       </TooltipProvider>
     </div>
diff --git a/libs/shared/lib/components/tooltip/Tooltip.tsx b/libs/shared/lib/components/tooltip/Tooltip.tsx
index 3541d2516..33c54848e 100644
--- a/libs/shared/lib/components/tooltip/Tooltip.tsx
+++ b/libs/shared/lib/components/tooltip/Tooltip.tsx
@@ -14,7 +14,7 @@ import {
   useInteractions,
   useMergeRefs,
   FloatingPortal,
-  FloatingArrow
+  FloatingArrow,
 } from '@floating-ui/react';
 import type { Placement } from '@floating-ui/react';
 import { FloatingDelayGroup } from '@floating-ui/react';
@@ -34,7 +34,7 @@ export function useTooltip({
   open: controlledOpen,
   onOpenChange: setControlledOpen,
   boundaryElement = null,
-  showArrow = false
+  showArrow = false,
 }: TooltipOptions = {}): {
   open: boolean;
   setOpen: (open: boolean) => void;
@@ -57,16 +57,16 @@ export function useTooltip({
       flip({
         crossAxis: placement.includes('-'),
         fallbackAxisSideDirection: 'start',
-        padding: 5
+        padding: 5,
       }),
       shift({ padding: 5 }),
     ],
-  }
+  };
 
   if (boundaryElement != null) {
     const boundary = boundaryElement?.current ?? undefined;
-    config.middleware.find(x => x.name == 'flip')!.options[0].boundary = boundary;
-    config.middleware.find(x => x.name == 'shift')!.options[0].boundary = boundary;
+    config.middleware.find((x) => x.name == 'flip')!.options[0].boundary = boundary;
+    config.middleware.find((x) => x.name == 'shift')!.options[0].boundary = boundary;
     config.middleware.push(hide({ boundary }));
   }
 
@@ -120,72 +120,69 @@ export function Tooltip({ children, ...options }: { children: React.ReactNode }
   // This can accept any props as options, e.g. `placement`,
   // or other positioning options.
   const tooltip = useTooltip(options);
-  
-  return <TooltipContext.Provider value={tooltip}>
-    {children}
-  </TooltipContext.Provider>;
+
+  return <TooltipContext.Provider value={tooltip}>{children}</TooltipContext.Provider>;
 }
 
-export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean, x?: number, y?: number }>(function TooltipTrigger(
-  { children, asChild = false, x = null, y = null, ...props },
-  propRef,
-) {
-  const context = useTooltipContext();
-  const childrenRef = React.useMemo(() => {
-    if (children == null) {
-      return null;
-    } else {
-      return (children as any).ref;
-    }
-  }, [children]);
-
-  const ref = useMergeRefs([context.data.refs.setReference, propRef, childrenRef]);
-
-  React.useEffect(() => {
-    if (x && y && context.data.refs.reference.current != null) {
-      const element = context.data.refs.reference.current as HTMLElement;
-      element.style.position = 'absolute';
-      const {x: offsetX, y: offsetY} = element.getBoundingClientRect();
-      element.getBoundingClientRect = () => {
-        return {
-          width: 0,
-          height: 0,
-          x: offsetX,
-          y: offsetY,
-          top: y + offsetY,
-          left: x + offsetX,
-          right: x + offsetX,
-          bottom: y + offsetY,
-        } as DOMRect
+export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean; x?: number; y?: number }>(
+  function TooltipTrigger({ children, asChild = false, x = null, y = null, ...props }, propRef) {
+    const context = useTooltipContext();
+    const childrenRef = React.useMemo(() => {
+      if (children == null) {
+        return null;
+      } else {
+        return (children as any).ref;
       }
-      context.data.update();
+    }, [children]);
+
+    const ref = useMergeRefs([context.data.refs.setReference, propRef, childrenRef]);
+
+    React.useEffect(() => {
+      if (x && y && context.data.refs.reference.current != null) {
+        const element = context.data.refs.reference.current as HTMLElement;
+        element.style.position = 'absolute';
+        const { x: offsetX, y: offsetY } = element.getBoundingClientRect();
+        element.getBoundingClientRect = () => {
+          return {
+            width: 0,
+            height: 0,
+            x: offsetX,
+            y: offsetY,
+            top: y + offsetY,
+            left: x + offsetX,
+            right: x + offsetX,
+            bottom: y + offsetY,
+          } as DOMRect;
+        };
+        context.data.update();
+      }
+    }, [x, y]);
+
+    // `asChild` allows the user to pass any element as the anchor
+    if (asChild && React.isValidElement(children)) {
+      return React.cloneElement(
+        children,
+        context.interactions.getReferenceProps({
+          ref,
+          ...props,
+          ...children.props,
+          'data-state': context.open ? 'open' : 'closed',
+        }),
+      );
     }
-  }, [x, y]);
-
-  // `asChild` allows the user to pass any element as the anchor
-  if (asChild && React.isValidElement(children)) {
-    return React.cloneElement(
-      children,
-      context.interactions.getReferenceProps({
-        ref,
-        ...props,
-        ...children.props,
-        'data-state': context.open ? 'open' : 'closed',
-      }),
-    );
-  }
 
-  return (
-    <div
-      ref={ref}
-      // The user can style the trigger based on the state
-      data-state={context.open ? 'open' : 'closed'}
-      {...context.interactions.getReferenceProps(props)}
-    >
-      {children}
-    </div>
-  );
-});
+    return (
+      <div
+        ref={ref}
+        // The user can style the trigger based on the state
+        data-state={context.open ? 'open' : 'closed'}
+        {...context.interactions.getReferenceProps(props)}
+      >
+        {children}
+      </div>
+    );
+  },
+);
 
 export const TooltipContent = React.forwardRef<
   HTMLDivElement,
@@ -210,12 +207,10 @@ export const TooltipContent = React.forwardRef<
         }}
         {...context.interactions.getFloatingProps(props)}
       >
-        { props.children }
-        { context.data.middlewareData.arrow ? <FloatingArrow 
-          ref={(context.data.refs as any).arrow} 
-          context={context.data.context} 
-          style={{fill: 'white'}}
-        /> : null }
+        {props.children}
+        {context.data.middlewareData.arrow ? (
+          <FloatingArrow ref={(context.data.refs as any).arrow} context={context.data.context} style={{ fill: 'white' }} />
+        ) : null}
       </div>
     </FloatingPortal>
   );
diff --git a/libs/shared/lib/schema/model/reactflow.tsx b/libs/shared/lib/schema/model/reactflow.tsx
index 39a121106..3af36d3b0 100644
--- a/libs/shared/lib/schema/model/reactflow.tsx
+++ b/libs/shared/lib/schema/model/reactflow.tsx
@@ -34,6 +34,8 @@ export type SchemaReactflowEntity = SchemaReactflowData & {
   // handles: string[];
   connectedRatio: number;
   name: string;
+  x: number;
+  y: number;
 };
 
 export type SchemaReactflowRelation = SchemaReactflowData & {
@@ -42,6 +44,8 @@ export type SchemaReactflowRelation = SchemaReactflowData & {
   collection: string;
   fromRatio: number;
   toRatio: number;
+  x: number;
+  y: number;
 };
 
 export type SchemaReactflowNodeWithFunctions = SchemaReactflowEntity & {
diff --git a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx
index d3bfe7ef2..3eb5e3abc 100644
--- a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx
+++ b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useRef } from 'react';
 import { Handle, Position, NodeProps } from 'reactflow';
 import { SchemaReactflowNodeWithFunctions } from '../../../model/reactflow';
 import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder';
@@ -6,10 +6,13 @@ import { SchemaEntityPopup } from './SchemaEntityPopup';
 import { Popup } from '@graphpolaris/shared/lib/components/Popup';
 import { SchemaNode } from '../../../model';
 import { EntityPill } from '@graphpolaris/shared/lib/components';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip';
+import { CardToolTipVis, CardToolTipVisProps } from '@graphpolaris/shared/lib/components/CardToolTipVis';
+import { StringDecoder } from 'string_decoder';
 
 export const SchemaEntityPill = React.memo(({ id, selected, data }: NodeProps<SchemaReactflowNodeWithFunctions>) => {
   const [openPopup, setOpenPopup] = useState(false);
-
+  const ref = useRef<HTMLDivElement>(null);
   /**
    * adds drag functionality in order to be able to drag the entityNode to the schema
    * @param event React Mouse drag event
@@ -22,6 +25,7 @@ export const SchemaEntityPill = React.memo(({ id, selected, data }: NodeProps<Sc
     };
     event.dataTransfer.setData('application/reactflow', JSON.stringify(eventData));
     event.dataTransfer.effectAllowed = 'move';
+    console.log('drag ', event);
   };
 
   /**
@@ -37,15 +41,44 @@ export const SchemaEntityPill = React.memo(({ id, selected, data }: NodeProps<Sc
   const onClickToggleAttributeAnalyticsPopupMenu = (): void => {
     data.toggleAttributeAnalyticsPopupMenu(id);
   };
+  //console.log(selected);
+  console.log(data);
 
   return (
     <>
+      {/* 
       {openPopup && (
         <Popup open={openPopup} hAnchor="left" className="-top-8" offset="-20rem">
           <SchemaEntityPopup data={data} onClose={() => setOpenPopup(false)} />
         </Popup>
       )}
+        */}
 
+      {openPopup && (
+        <Tooltip key={data.name} open={true} boundaryElement={ref} showArrow={true}>
+          <TooltipTrigger x={data.x} y={data.y} />
+          <TooltipContent side="top">
+            <div>
+              <CardToolTipVis
+                type="schema"
+                typeOfSchema="node"
+                name={data.name}
+                colorHeader="#fb7b04"
+                numberOfElements={1000}
+                data={data.attributes.reduce(
+                  (acc, attr) => {
+                    if (attr.name && attr.type) {
+                      acc[attr.name] = attr.type;
+                    }
+                    return acc;
+                  },
+                  {} as Record<string, string>,
+                )}
+              />
+            </div>
+          </TooltipContent>
+        </Tooltip>
+      )}
       <div
         className="w-fit h-fit"
         onDragStart={(event) => onDragStart(event)}
diff --git a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx
index c0f5c9247..9514679eb 100644
--- a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx
+++ b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx
@@ -31,6 +31,7 @@ export const SchemaEntityPopup = (props: SchemaEntityPopupProps) => {
   return (
     // <FormDiv hAnchor="left">
     <>
+      {/* 
       <FormCard>
         <FormBody
           onSubmit={(e: FormEvent<HTMLFormElement>) => {
@@ -79,6 +80,7 @@ export const SchemaEntityPopup = (props: SchemaEntityPopupProps) => {
           </FormControl>
         </FormBody>
       </FormCard>
+    */}
     </>
   );
 };
diff --git a/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx
index 86cdfcdb8..68eb3d1fc 100644
--- a/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx
+++ b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useRef } from 'react';
 import { Handle, Position, NodeProps } from 'reactflow';
 import { SchemaReactflowRelationWithFunctions } from '../../../model/reactflow';
 import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder';
@@ -7,8 +7,12 @@ import { SchemaRelationshipPopup } from './SchemaRelationshipPopup';
 import { SchemaEdge } from '../../../model';
 import { RelationPill } from '@graphpolaris/shared/lib/components';
 
+import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip';
+import { CardToolTipVis, CardToolTipVisProps } from '@graphpolaris/shared/lib/components/CardToolTipVis';
+
 export const SchemaRelationPill = React.memo(({ id, selected, data, ...props }: NodeProps<SchemaReactflowRelationWithFunctions>) => {
   const [openPopup, setOpenPopup] = useState(false);
+  const ref = useRef<HTMLDivElement>(null);
 
   /**
    * Adds drag functionality in order to be able to drag the relationNode to the schema.
@@ -43,14 +47,49 @@ export const SchemaRelationPill = React.memo(({ id, selected, data, ...props }:
   const onClickToggleAttributeAnalyticsPopupMenu = (): void => {
     data.toggleAttributeAnalyticsPopupMenu(data.collection);
   };
-
+  console.log('relation ', data);
   return (
     <>
+      {/* 
       {openPopup && (
         <Popup open={openPopup} hAnchor="left" className="-top-8" offset="-20rem">
           <SchemaRelationshipPopup data={data} onClose={() => setOpenPopup(false)} />
         </Popup>
       )}
+        */}
+
+      {openPopup && (
+        <Tooltip key={data.name} open={true} boundaryElement={ref} showArrow={true}>
+          <TooltipTrigger x={data.x} y={data.y} />
+          <TooltipContent side="top">
+            <div>
+              <CardToolTipVis
+                type="schema"
+                typeOfSchema="relationship"
+                name={data.collection}
+                colorHeader="#0676C1"
+                numberOfElements={1000}
+                connectedFrom={data.from}
+                connectedTo={data.to}
+                data={
+                  data.attributes.length > 0
+                    ? data.attributes.reduce(
+                        (acc, attr) => {
+                          if (attr.name && attr.type) {
+                            acc[attr.name] = attr.type;
+                          }
+                          return acc;
+                        },
+                        {} as Record<string, string>,
+                      )
+                    : {}
+                }
+              />
+            </div>
+          </TooltipContent>
+        </Tooltip>
+      )}
+
       <div
         className="w-fit h-fit"
         onDragStart={(event) => onDragStart(event)}
-- 
GitLab