From 53e431a676ea6df334a5f2b7796532adaf3b9d34 Mon Sep 17 00:00:00 2001
From: Leonardo Christino <leomilho@gmail.com>
Date: Wed, 13 Sep 2023 11:20:29 +0200
Subject: [PATCH] feat(alert): extra time for alerts and allow cancel operation
 by user click

---
 .../authorization/dashboardAlerts.tsx         | 66 +++++++++++++------
 libs/shared/package.json                      |  2 +
 pnpm-lock.yaml                                | 34 ++++------
 3 files changed, 61 insertions(+), 41 deletions(-)

diff --git a/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx b/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx
index c7744b58e..f67aa55e4 100644
--- a/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx
+++ b/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx
@@ -1,10 +1,11 @@
-import React, { ReactNode, useEffect, useState } from 'react';
+import React, { ReactNode, useEffect, useState, useImperativeHandle, useRef } from 'react';
 import Broker from '@graphpolaris/shared/lib/data-access/socket/broker';
 import { useImmer } from 'use-immer';
 import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 import { useAppDispatch, useConfig } from '../store';
 import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
 import { removeLastError, removeLastWarning } from '../store/configSlice';
+import { includes } from 'lodash-es';
 
 type Message = {
   message: ReactNode;
@@ -12,32 +13,52 @@ type Message = {
 };
 
 export const DashboardAlerts = (props: { timer?: number }) => {
-  const [messages, setMessages] = useImmer<{ data: any; routingKey: string; message: Message; showing: boolean }[]>([]);
-  const timer = props.timer || 1200;
+  const [messages, setMessages] = useImmer<{ data: any; routingKey: string; message: Message; showing: boolean; key: string }[]>([]);
+  const timer = props.timer || 4000;
   const config = useConfig();
   const dispatch = useAppDispatch();
+  const ref = useRef<any>(null);
 
-  async function processMessage(message: Message | undefined, data: any, routingKey: string) {
-    // Queue the message to be shown for props.time time, then fade it out matching the animation of animate-closemenu fade out time
-    if (message) {
+  useImperativeHandle(ref, () => ({
+    onAdd: (message: Message, data: any, routingKey: string) => {
+      let newMessage = { data, routingKey, message: message as Message, showing: true, key: `${Date.now()}-${Math.random()}` };
+      setMessages((draft) => {
+        draft.unshift(newMessage);
+        return draft;
+      });
+      return newMessage.key;
+    },
+    onTimeout: (key: string) => {
+      setMessages((draft) => {
+        const idx = draft.findIndex((m) => m.key === key);
+        if (idx === -1) return draft;
+        draft.splice(idx, 1);
+        return draft;
+      });
+    },
+    onShowTimeout: (key: string) => {
       setMessages((draft) => {
-        draft.unshift({ data, routingKey, message: message as Message, showing: true });
+        const idx = draft.findIndex((m) => m.key === key);
+        if (idx === -1) return draft;
+        draft[idx].showing = false;
         return draft;
       });
+
       setTimeout(() => {
-        setMessages((draft) => {
-          draft[draft.length - 1].showing = false;
-          // draft.pop();
-          return draft;
-        });
+        ref.current.onTimeout(key);
+        return true;
+      }, 300); // matches the animation of animate-closemenu
+    },
+  }));
 
-        setTimeout(() => {
-          setMessages((draft) => {
-            draft.pop();
-            return draft;
-          });
-          return true;
-        }, 300); // matches the animation of animate-closemenu
+  async function processMessage(message: Message | undefined, data: any, routingKey: string) {
+    // Queue the message to be shown for props.time time, then fade it out matching the animation of animate-closemenu fade out time
+    if (message && ref?.current) {
+      const key = ref.current.onAdd(message, data, routingKey);
+
+      setTimeout(() => {
+        ref.current.onShowTimeout(key);
+        return true;
       }, timer);
     }
   }
@@ -125,13 +146,18 @@ export const DashboardAlerts = (props: { timer?: number }) => {
             <div
               key={i}
               className={
-                `absolute bottom-5 right-5 w-fit min-w-[20rem] alert z-50 ` +
+                `absolute bottom-5 right-5 w-fit min-w-[20rem] cursor-pointer alert z-50 ` +
                 (m.showing ? 'animate-openmenu ' : 'animate-closemenu ') +
                 (m.message?.className || '')
               }
               style={{
                 transform: `translateY(-${i * 5}rem)`,
               }}
+              onClick={(e) => {
+                e.preventDefault();
+                e.stopPropagation();
+                ref.current.onShowTimeout(m.key);
+              }}
             >
               <span className="flex flex-row content-center gap-3 text-white">{m.message.message}</span>
               {/* {!message && (m.data?.status ? m.data.status : m.routingKey)} */}
diff --git a/libs/shared/package.json b/libs/shared/package.json
index 66a91ab1e..5a7fd7345 100644
--- a/libs/shared/package.json
+++ b/libs/shared/package.json
@@ -50,6 +50,7 @@
     "jspdf": "^2.5.1",
     "kepler.gl": "^2.5.5",
     "keycloak-js": "^21.1.1",
+    "lodash-es": "^4.17.21",
     "pixi-viewport": "^5.0.2",
     "pixi.js": "^7.1.4",
     "react-color": "^2.19.3",
@@ -80,6 +81,7 @@
     "@testing-library/react-hooks": "8.0.1",
     "@types/color": "^3.0.3",
     "@types/d3": "^7.4.0",
+    "@types/lodash-es": "^4.17.9",
     "@types/node": "18.13.0",
     "@types/react": "^18.0.27",
     "@types/react-color": "^3.0.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 61424fc30..05e29cb6f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -313,6 +313,9 @@ importers:
       keycloak-js:
         specifier: ^21.1.1
         version: 21.1.1
+      lodash-es:
+        specifier: ^4.17.21
+        version: 4.17.21
       pixi-viewport:
         specifier: ^5.0.2
         version: 5.0.2
@@ -398,6 +401,9 @@ importers:
       '@types/d3':
         specifier: ^7.4.0
         version: 7.4.0
+      '@types/lodash-es':
+        specifier: ^4.17.9
+        version: 4.17.9
       '@types/node':
         specifier: 18.13.0
         version: 18.13.0
@@ -662,7 +668,7 @@ importers:
         version: 8.7.0(eslint@7.32.0)
       eslint-config-turbo:
         specifier: latest
-        version: 1.10.12(eslint@7.32.0)
+        version: 1.10.13(eslint@7.32.0)
       eslint-plugin-react:
         specifier: 7.31.8
         version: 7.31.8(eslint@7.32.0)
@@ -8707,6 +8713,12 @@ packages:
   /@types/json5@0.0.29:
     resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
 
+  /@types/lodash-es@4.17.9:
+    resolution: {integrity: sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==}
+    dependencies:
+      '@types/lodash': 4.14.191
+    dev: true
+
   /@types/lodash@4.14.191:
     resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
     dev: true
@@ -12241,15 +12253,6 @@ packages:
     dependencies:
       eslint: 7.32.0
 
-  /eslint-config-turbo@1.10.12(eslint@7.32.0):
-    resolution: {integrity: sha512-z3jfh+D7UGYlzMWGh+Kqz++hf8LOE96q3o5R8X4HTjmxaBWlLAWG+0Ounr38h+JLR2TJno0hU9zfzoPNkR9BdA==}
-    peerDependencies:
-      eslint: '>6.6.0'
-    dependencies:
-      eslint: 7.32.0
-      eslint-plugin-turbo: 1.10.12(eslint@7.32.0)
-    dev: false
-
   /eslint-config-turbo@1.10.13(eslint@7.32.0):
     resolution: {integrity: sha512-Ffa0SxkRCPMtfUX/HDanEqsWoLwZTQTAXO9W4IsOtycb2MzJDrVcLmoFW5sMwCrg7gjqbrC4ZJoD+1SPPzIVqg==}
     peerDependencies:
@@ -12257,7 +12260,6 @@ packages:
     dependencies:
       eslint: 7.32.0
       eslint-plugin-turbo: 1.10.13(eslint@7.32.0)
-    dev: true
 
   /eslint-import-resolver-node@0.3.7:
     resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
@@ -12400,15 +12402,6 @@ packages:
       semver: 6.3.0
       string.prototype.matchall: 4.0.8
 
-  /eslint-plugin-turbo@1.10.12(eslint@7.32.0):
-    resolution: {integrity: sha512-uNbdj+ohZaYo4tFJ6dStRXu2FZigwulR1b3URPXe0Q8YaE7thuekKNP+54CHtZPH9Zey9dmDx5btAQl9mfzGOw==}
-    peerDependencies:
-      eslint: '>6.6.0'
-    dependencies:
-      dotenv: 16.0.3
-      eslint: 7.32.0
-    dev: false
-
   /eslint-plugin-turbo@1.10.13(eslint@7.32.0):
     resolution: {integrity: sha512-el4AAmn0zXmvHEyp1h0IQMfse10Vy8g5Vbg4IU3+vD9CSj5sDbX07iFVt8sCKg7og9Q5FAa9mXzlCf7t4vYgzg==}
     peerDependencies:
@@ -12416,7 +12409,6 @@ packages:
     dependencies:
       dotenv: 16.0.3
       eslint: 7.32.0
-    dev: true
 
   /eslint-scope@5.1.1:
     resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
-- 
GitLab