From c0b3a7d02d851ecdb7facee356e37670b6858766 Mon Sep 17 00:00:00 2001
From: Samed <sbalcioglu@graphpolaris.com>
Date: Mon, 2 Dec 2024 11:38:37 +0100
Subject: [PATCH 1/9] feat: report backend implementation

---
 .../lib/components/textEditor/TextEditor.tsx  |   4 +-
 .../textEditor/plugins/ToolbarPlugin.tsx      |   5 +-
 libs/shared/lib/data-access/api/apiService.ts | 106 ++++++++
 libs/shared/lib/data-access/broker/broker.tsx |   6 +-
 libs/shared/lib/data-access/broker/types.ts   |   2 +-
 .../data-access/broker/wsInsightSharing.ts    |  75 ++++++
 .../data-access/store/insightSharingSlice.ts  |  70 ++++++
 libs/shared/lib/data-access/store/store.ts    |   2 +
 .../lib/insight-sharing/InsightDialog.tsx     |  10 +-
 .../lib/insight-sharing/SettingsPanel.tsx     |  85 +++++--
 .../insight-sharing/alerting/AlertingForm.tsx | 147 +++++++++++-
 .../insight-sharing/components/AddItem.tsx    |  59 ++++-
 .../insight-sharing/components/Sidebar.tsx    |  45 +++-
 .../reporting/ReportingForm.tsx               | 226 ++++++++++++++----
 14 files changed, 743 insertions(+), 99 deletions(-)
 create mode 100644 libs/shared/lib/data-access/api/apiService.ts
 create mode 100644 libs/shared/lib/data-access/broker/wsInsightSharing.ts
 create mode 100644 libs/shared/lib/data-access/store/insightSharingSlice.ts

diff --git a/libs/shared/lib/components/textEditor/TextEditor.tsx b/libs/shared/lib/components/textEditor/TextEditor.tsx
index 2b931b872..5c8714452 100644
--- a/libs/shared/lib/components/textEditor/TextEditor.tsx
+++ b/libs/shared/lib/components/textEditor/TextEditor.tsx
@@ -15,7 +15,7 @@ import { VariableNode } from './VariableNode';
 import { fontFamily } from 'html2canvas/dist/types/css/property-descriptors/font-family';
 
 type TextEditorProps = {
-  editorState: EditorState | undefined;
+  editorState: EditorState | null;
   setEditorState: (value: EditorState) => void;
   showToolbar: boolean;
   placeholder?: string;
@@ -31,7 +31,7 @@ export function TextEditor({ editorState, setEditorState, showToolbar, placehold
 
   const initialConfig = {
     namespace: 'MyEditor',
-    editorState: editorState,
+    editorState: editorState  ? JSON.stringify(editorState) : null,
     onError: ErrorHandler,
     nodes: [VariableNode],
   };
diff --git a/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx b/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx
index e4b1ec07e..041367db0 100644
--- a/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx
+++ b/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx
@@ -19,8 +19,9 @@ export function ToolbarPlugin() {
   };
 
   return [
-    <div style={{ flex: '1 1 auto' }}></div>,
+    <div key= "spacer" style={{ flex: '1 1 auto' }}></div>,
     <Button
+      key = "bold"
       className="my-2"
       variantType="secondary"
       variant="ghost"
@@ -29,6 +30,7 @@ export function ToolbarPlugin() {
       onClick={formatBold}
     />,
     <Button
+      key="italic"
       className="my-2"
       variantType="secondary"
       variant="ghost"
@@ -37,6 +39,7 @@ export function ToolbarPlugin() {
       onClick={formatItalic}
     />,
     <Button
+      key="underline"
       className="my-2 me-2"
       variantType="secondary"
       variant="ghost"
diff --git a/libs/shared/lib/data-access/api/apiService.ts b/libs/shared/lib/data-access/api/apiService.ts
new file mode 100644
index 000000000..a9ea11cfe
--- /dev/null
+++ b/libs/shared/lib/data-access/api/apiService.ts
@@ -0,0 +1,106 @@
+import axios from 'axios';
+import { Insight } from '../store/insightSharingSlice';
+
+const API_BASE_URL = 'http://localhost:8000/api';
+
+const axiosInstance = axios.create({
+    baseURL: API_BASE_URL,
+    headers: {
+      'Content-Type': 'application/json',
+    }
+  });
+
+  export async function createReport(reportData: Partial<Insight>) {
+    console.log('Creating report with data:', reportData);
+    try {
+      const dataToSend = { ...reportData };
+      delete dataToSend.id;
+      delete dataToSend.type;
+
+      const response = await axiosInstance.post('/report/', dataToSend);
+      console.log('Report creation response:', response);
+
+      const report = { ...response.data, type: 'report' };
+
+      return report;
+    } catch (error) {
+      console.error('Error creating report:', error);
+      if (axios.isAxiosError(error)) {
+        console.error('Response data:', error.response?.data);
+        console.error('Request URL:', error.config?.url);
+      }
+      throw error;
+    }
+  }
+
+  export async function getReports(saveStateId: string) {
+    console.log('Fetching reports for saveStateId:', saveStateId);
+    try {
+      const response = await axiosInstance.get(`/report/save_state/${saveStateId}/`);
+      console.log('Reports fetch response:', response);
+      const reports = response.data.map((report: Insight) => ({
+        ...report,
+        type: 'report',
+      }));
+  
+      return reports;
+    } catch (error) {
+      console.error('Error fetching reports:', error);
+      if (axios.isAxiosError(error)) {
+        console.error('Response data:', error.response?.data);
+        console.error('Request URL:', error.config?.url);
+      }
+      return [];
+    }
+  }
+
+  export async function updateReport(reportId: string, reportData: Partial<Insight>) {
+    try {
+      console.log('Updating report with data:', reportData);
+      const response = await axiosInstance.put(`/report/${reportId}/`, reportData);
+      return response.data;
+    } catch (error) {
+      console.error('Error updating report:', error);
+      throw error;
+    }
+  }
+
+    export async function deleteReport(reportId: string) {
+    try {
+        const response = await axiosInstance.delete(`report/${reportId}/`);
+        return response.data;
+    } catch (error) {
+        console.error('Error deleting report:', error);
+        throw error;
+    }
+    }
+
+    export async function createAlert(alertData: any) {
+        try {
+        const response = await axiosInstance.post('alert/', alertData);
+        return response.data;
+        } catch (error) {
+        console.error('Error creating alert:', error);
+        throw error;
+        }
+    }
+    
+    export async function updateAlert(alertId: string, alertData: any) {
+        try {
+        const response = await axiosInstance.put(`alert/${alertId}`, alertData);
+        return response.data;
+        } catch (error) {
+        console.error('Error updating alert:', error);
+        throw error;
+        }
+    }
+    
+    export async function deleteAlert(alertId: string) {
+        try {
+        const response = await axiosInstance.delete(`alert/${alertId}`);
+        return response.data;
+        } catch (error) {
+        console.error('Error deleting alert:', error);
+        throw error;
+        }
+    }
diff --git a/libs/shared/lib/data-access/broker/broker.tsx b/libs/shared/lib/data-access/broker/broker.tsx
index 41cfe6f8e..ff09fb28a 100644
--- a/libs/shared/lib/data-access/broker/broker.tsx
+++ b/libs/shared/lib/data-access/broker/broker.tsx
@@ -200,9 +200,9 @@ export class Broker {
     }
 
     fullMessage.sessionID = this.authHeader?.sessionID ?? '';
-    if (message.body && typeof message.body !== 'string') {
-      fullMessage.body = JSON.stringify(message.body);
-    }
+    //if (message.body && typeof message.body !== 'string') {
+     // fullMessage.body = JSON.stringify(message.body);
+    //}
 
     if (this.webSocket && this.connected && this.webSocket.readyState === 1) this.webSocket.send(JSON.stringify(fullMessage));
     else
diff --git a/libs/shared/lib/data-access/broker/types.ts b/libs/shared/lib/data-access/broker/types.ts
index dc3dd7c22..462762e18 100644
--- a/libs/shared/lib/data-access/broker/types.ts
+++ b/libs/shared/lib/data-access/broker/types.ts
@@ -19,7 +19,7 @@ type QueryOrchestratorMessage = {
   queryID: string;
 };
 
-export type keyTypeI = 'broadcastState' | 'dbConnection' | 'schema' | 'query' | 'state' | 'user';
+export type keyTypeI = 'broadcastState' | 'dbConnection' | 'schema' | 'query' | 'state' | 'user' | 'insight'| 'report' | 'alert';
 export type subKeyTypeI =
   // Crud
   | 'create'
diff --git a/libs/shared/lib/data-access/broker/wsInsightSharing.ts b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
new file mode 100644
index 000000000..98f18b7ec
--- /dev/null
+++ b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
@@ -0,0 +1,75 @@
+import { Broker } from './broker';
+import { Insight, InsightType } from '../store/insightSharingSlice';
+
+type GetInsightsResponse = (data: { reports: Insight[]; alerts: Insight[] }, status: string) => void;
+export function wsGetInsights(callback?: GetInsightsResponse) {
+  const internalCallback: GetInsightsResponse = (data, status) => {
+    if (callback) callback(data, status);
+  };
+
+  Broker.instance().sendMessage(
+    {
+      key: 'insight',
+      subKey: 'getAll',
+    },
+    internalCallback
+  );
+}
+
+export function wsCreateInsight(insight: Insight, callback?: Function) {
+  const messageKey = insight.type === 'report' ? 'report' : 'alert';
+  Broker.instance().sendMessage(
+    {
+      key: messageKey,
+      subKey: 'create',
+      body: insight,
+    },
+    (data: any, status: string) => {
+      if (status === 'Bad Request') {
+        console.error('Failed to create insight:', data);
+      }
+      if (callback) callback(data, status);
+    }
+  );
+}
+
+export function wsUpdateInsight(insight: Insight, callback?: Function) {
+  const messageKey = insight.type === 'report' ? 'report' : 'alert';
+  Broker.instance().sendMessage(
+    {
+      key: messageKey,
+      subKey: 'update',
+      body: insight,
+    },
+    (data: any, status: string) => {
+      if (status === 'Bad Request') {
+        console.error('Failed to update insight:', data);
+      }
+      if (callback) callback(data, status);
+    }
+  );
+}
+
+export function wsDeleteInsight(id: string, type: InsightType, callback?: Function) {
+  const messageKey = type === 'report' ? 'report' : 'alert';
+  Broker.instance().sendMessage(
+    {
+      key: messageKey,
+      subKey: 'delete',
+      body: { id },
+    },
+    (data: any, status: string) => {
+      if (status === 'Bad Request') {
+        console.error('Failed to delete insight:', data);
+      }
+      if (callback) callback(data, status);
+    }
+  );
+}
+
+export function wsInsightSubscription(callback: (data: any, status: string) => void) {
+  const id = Broker.instance().subscribe(callback, 'insight_result');
+  return () => {
+    Broker.instance().unSubscribe('insight_result', id);
+  };
+}
diff --git a/libs/shared/lib/data-access/store/insightSharingSlice.ts b/libs/shared/lib/data-access/store/insightSharingSlice.ts
new file mode 100644
index 000000000..c2859b576
--- /dev/null
+++ b/libs/shared/lib/data-access/store/insightSharingSlice.ts
@@ -0,0 +1,70 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { RootState } from '../store';
+
+export type InsightType = 'report' | 'alert';
+
+export interface Insight {
+  id: string;
+  name: string;
+  description: string;
+  recipients: string[];
+  frequency: string;
+  template: string;
+  save_state_id: string;
+  createdAt: string;
+  updatedAt: string;
+  type: 'report' | 'alert';
+}
+
+interface InsightState {
+  insights: Insight[];
+  reports: Insight[];
+  alerts: Insight[];
+}
+
+const initialState: InsightState = {
+  reports: [],
+  alerts: [],
+  insights: []
+};
+
+const insightSharingSlice = createSlice({
+  name: 'insightSharing',
+  initialState,
+  reducers: {
+    setInsights(state, action: PayloadAction<Insight[] | { reports: Insight[] }>) {
+      const insights = Array.isArray(action.payload) ? action.payload : action.payload.reports;
+      state.reports = insights.filter((insight) => insight.type === 'report');
+      state.alerts = insights.filter((insight) => insight.type === 'alert');
+    },
+    addInsight(state, action: PayloadAction<Insight>) {
+      if (action.payload.type === 'report') {
+        state.reports.push(action.payload);
+      } else {
+        state.alerts.push(action.payload);
+      }
+    },
+    updateInsight(state, action: PayloadAction<Insight>) {
+      const insights = action.payload.type === 'report' ? state.reports : state.alerts;
+      const index = insights.findIndex((i) => i.id === action.payload.id);
+      if (index !== -1) {
+        insights[index] = { ...action.payload };
+      } else {
+        insights.push(action.payload);
+      }
+    },
+    deleteInsight(state, action: PayloadAction<{ id: string; type: 'report' | 'alert' }>) {
+      const insights = action.payload.type === 'report' ? state.reports : state.alerts;
+      const index = insights.findIndex((i) => i.id === action.payload.id);
+      if (index !== -1) {
+        insights.splice(index, 1);
+      }
+    },
+  },
+});
+
+export const { setInsights, addInsight, updateInsight, deleteInsight } = insightSharingSlice.actions;
+export const selectReports = (state: RootState): Insight[] => state.insightSharing.reports;
+export const selectAlerts = (state: RootState): Insight[] => state.insightSharing.alerts;
+
+export default insightSharingSlice.reducer;
diff --git a/libs/shared/lib/data-access/store/store.ts b/libs/shared/lib/data-access/store/store.ts
index c1aea4606..3b1ea1c4d 100644
--- a/libs/shared/lib/data-access/store/store.ts
+++ b/libs/shared/lib/data-access/store/store.ts
@@ -11,6 +11,7 @@ import visualizationSlice from './visualizationSlice';
 import interactionSlice from './interactionSlice';
 import policyUsersSlice from './authorizationUsersSlice';
 import policyPermissionSlice from './authorizationResourcesSlice';
+import insightSharingSlice from './insightSharingSlice';
 
 export const store = configureStore({
   reducer: {
@@ -26,6 +27,7 @@ export const store = configureStore({
     visualize: visualizationSlice,
     policyUsers: policyUsersSlice,
     policyResources: policyPermissionSlice,
+    insightSharing: insightSharingSlice,
   },
   middleware: (getDefaultMiddleware) =>
     getDefaultMiddleware({
diff --git a/libs/shared/lib/insight-sharing/InsightDialog.tsx b/libs/shared/lib/insight-sharing/InsightDialog.tsx
index 0af6191f2..71d93bf91 100644
--- a/libs/shared/lib/insight-sharing/InsightDialog.tsx
+++ b/libs/shared/lib/insight-sharing/InsightDialog.tsx
@@ -8,16 +8,13 @@ type Props = {
   onClose: () => void;
 };
 
-const reports = ['Sequence 1', 'Sequence 2'];
-const alerts = ['Potential New Incident', 'Potential New Info'];
-
 export function InsightDialog(props: Props) {
   const [adding, setAdding] = useState<boolean>(false);
   const [active, setActive] = useState<string>('');
   const [activeCategory, setActiveCategory] = useState<MonitorType | undefined>(undefined);
 
-  const handleChangeActive = (category: MonitorType, name: string) => {
-    setActive(name);
+  const handleChangeActive = (category: MonitorType, id: string) => {
+    setActive(id);
     setActiveCategory(category);
   };
 
@@ -31,8 +28,6 @@ export function InsightDialog(props: Props) {
       <DialogContent className="w-5/6 h-5/6 rounded-none py-0 px-0">
         <div className="flex w-full h-full">
           <Sidebar
-            reports={reports}
-            alerts={alerts}
             changeActive={handleChangeActive}
             setAdding={setAdding}
             setActiveCategory={setActiveCategory}
@@ -43,6 +38,7 @@ export function InsightDialog(props: Props) {
             adding={adding}
             setAdding={setAdding}
             setActiveCategory={setActiveCategory}
+            setActive={setActive}
           />
         </div>
       </DialogContent>
diff --git a/libs/shared/lib/insight-sharing/SettingsPanel.tsx b/libs/shared/lib/insight-sharing/SettingsPanel.tsx
index 9d0a7a676..54c65d266 100644
--- a/libs/shared/lib/insight-sharing/SettingsPanel.tsx
+++ b/libs/shared/lib/insight-sharing/SettingsPanel.tsx
@@ -1,10 +1,17 @@
 import React, { useEffect, useState } from 'react';
 import { ReportingForm } from './reporting/ReportingForm';
 import { AlertingForm } from './alerting/AlertingForm';
-import { Button, Icon } from '../components';
 import { AddItem } from './components/AddItem';
 import { StartScreen } from './components/StartScreen';
 import { MonitorType } from './components/Sidebar';
+import { useAppDispatch, useAppSelector } from '../data-access';
+import {
+  deleteInsight,
+  selectReports,
+  selectAlerts,
+  Insight,
+} from '../data-access/store/insightSharingSlice';
+import { wsDeleteInsight } from '../data-access/broker/wsInsightSharing';
 
 type Props = {
   active: string;
@@ -12,42 +19,70 @@ type Props = {
   adding: boolean;
   setAdding: (val: boolean) => void;
   setActiveCategory: (val: MonitorType | undefined) => void;
+  setActive: (val: string) => void;
 };
 
 export function SettingsPanel(props: Props) {
-  const [contacts, setContacts] = useState<string[]>([]);
+  const dispatch = useAppDispatch();
+  const reports = useAppSelector(selectReports);
+  const alerts = useAppSelector(selectAlerts);
 
-  const getContacts = (): string[] => ['Jan', 'Piet'];
+  const [currentInsight, setCurrentInsight] = useState<Insight | null>(null);
 
   useEffect(() => {
-    if (!contacts) {
-      const userContacts = getContacts();
-      setContacts(userContacts);
+    if (props.activeCategory && props.active) {
+      const insights = props.activeCategory === 'report' ? reports : alerts;
+      const insight = insights.find((i) => i.id === props.active);
+      if (insight) {
+        setCurrentInsight(insight);
+      }
+    } else {
+      setCurrentInsight(null);
     }
-  }, []);
+  }, [props.activeCategory, props.active, reports, alerts]);
+
+  const handleDelete = () => {
+    if (currentInsight) {
+      dispatch(deleteInsight({ id: currentInsight.id, type: currentInsight.type }));
+      wsDeleteInsight(currentInsight.id, currentInsight.type);
+      props.setActive('');
+      props.setActiveCategory(undefined);
+    }
+  };
 
   return props.activeCategory ? (
     <div className="w-3/4 p-4">
       {props.adding ? (
-        <AddItem category={props.activeCategory} />
-      ) : (
-        props.active &&
-        props.activeCategory && (
-          <div>
-            {props.activeCategory === 'report' ? (
-              <ReportingForm activeTemplate={props.active} />
-            ) : (
-              <AlertingForm activeTemplate={props.active} />
-            )}
-          </div>
-        )
-      )}
-      <div className="flex justify-end mt-2">
-        <Button label="Delete" variantType="secondary" variant="outline" />
-        <Button label="Save" variantType="primary" className="ml-2" />
-      </div>
+        <AddItem
+          category={props.activeCategory}
+          setAdding={props.setAdding}
+          setActive={props.setActive}
+          setActiveCategory={props.setActiveCategory}
+        />
+      ) : currentInsight ? (
+        <div>
+          {props.activeCategory === 'report' ? (
+            <ReportingForm
+              insight={currentInsight}
+              setActiveCategory={props.setActiveCategory}
+              setActive={props.setActive}
+              handleDelete={handleDelete}
+            />
+          ) : (
+            <AlertingForm
+              insight={currentInsight}
+              setActiveCategory={props.setActiveCategory}
+              setActive={props.setActive}
+              handleDelete={handleDelete}
+            />
+          )}
+        </div>
+      ) : null}
     </div>
   ) : (
-    <StartScreen setAdding={props.setAdding} setActiveCategory={props.setActiveCategory} />
+    <StartScreen
+      setAdding={props.setAdding}
+      setActiveCategory={props.setActiveCategory}
+    />
   );
 }
diff --git a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
index 11ea8d907..1870a1ffd 100644
--- a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
+++ b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
@@ -1,18 +1,135 @@
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
 import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '../../components/accordion';
 import { TextEditor } from '../../components/textEditor';
+import { Insight, updateInsight, deleteInsight } from '../../data-access/store/insightSharingSlice';
+import { useAppDispatch, useSessionCache } from '../../data-access';
+import { Button, Input, LoadingSpinner } from '../../components';
 import { EditorState } from 'lexical';
+import { createAlert, updateAlert, deleteAlert } from '../../data-access/api/apiService';
+import { MonitorType } from '../components/Sidebar';
 
 type Props = {
-  activeTemplate: string;
+  insight: Insight;
+  setActiveCategory?: (val: MonitorType | undefined) => void;
+  setActive?: (val: string) => void;
+  handleDelete: () => void;
 };
 
 export function AlertingForm(props: Props) {
-  const [editorState, setEditorState] = useState<EditorState | undefined>(undefined);
+  const dispatch = useAppDispatch();
+  const session = useSessionCache();
+  const [loading, setLoading] = useState(false);
+  const [name, setName] = useState(props.insight.name);
+  const [description, setDescription] = useState(props.insight.description);
+  const [recipients, setRecipients] = useState<string[]>(props.insight.recipients || []);
+  const [recipientInput, setRecipientInput] = useState<string>('');
+  const [editorState, setEditorState] = useState<EditorState | null>(null);
+
+  useEffect(() => {
+    setName(props.insight.name);
+    setDescription(props.insight.description);
+    setRecipientInput(props.insight.recipients ? props.insight.recipients.join(', ') : '');
+    setRecipients(props.insight.recipients || []);
+    if (props.insight.template) {
+      try {
+        const parsedTemplate = JSON.parse(props.insight.template);
+        setEditorState(parsedTemplate);
+      } catch (e) {
+        setEditorState(null);
+        console.error('Failed to parse template:', e);
+      }
+    } else {
+      setEditorState(null);
+    }
+  }, [props.insight]);
+
+  const handleSave = async () => {
+    if (!name || name.trim() === '') {
+      alert('Please enter a name for the alert.');
+      return;
+    }
+    if (!description || description.trim() === '') {
+      alert('Please enter a description for the alert.');
+      return;
+    }
+    if (!editorState) {
+      alert('Please enter content in the text editor before saving.');
+      return;
+    }
+
+    const alertData = {
+      id: props.insight.id,
+      name,
+      description,
+      recipients,
+      template: JSON.stringify(editorState),
+      save_state_id: session.currentSaveState || '',
+      type: 'alert' as const,
+      frequency: ''
+    };
+
+    setLoading(true);
+    try {
+      let savedAlert;
+      if (props.insight.id) {
+        savedAlert = await updateAlert(props.insight.id, alertData);
+        dispatch(updateInsight(savedAlert));
+      } else {
+        savedAlert = await createAlert(alertData);
+        dispatch(updateInsight(savedAlert));
+      }
+      console.log('Alert saved successfully');
+    } catch (error) {
+      console.error('Failed to save alert:', error);
+      alert('Failed to save alert.');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleDelete = async () => {
+    if (!props.insight.id) {
+      alert('Cannot delete an alert without an ID.');
+      return;
+    }
+
+    setLoading(true);
+    try {
+      await deleteAlert(props.insight.id);
+      dispatch(deleteInsight({ id: props.insight.id, type: props.insight.type }));
+      if (props.setActive && props.setActiveCategory) {
+        props.setActive('');
+        props.setActiveCategory(undefined);
+      }
+    } catch (error) {
+      console.error('Failed to delete alert:', error);
+      alert('Failed to delete alert.');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  if (loading) {
+    return <LoadingSpinner />;
+  }
 
   return (
     <div>
-      <span className="text-lg text-secondary-600 font-bold mb-4">Alert ID: {props.activeTemplate}</span>
+      <span className="text-lg text-secondary-600 font-bold mb-4">Alert ID: {props.insight.id}</span>
+      <Input
+        label="Name"
+        type="text"
+        value={name}
+        onChange={setName}
+        className="mb-4"
+      />
+      <Input
+        label="Description"
+        type="text"
+        value={description}
+        onChange={setDescription}
+        className="mb-4"
+      />
       <Accordion defaultOpenAll={true} className="border-t divide-y">
         <AccordionItem className="pt-2 pb-4">
           <AccordionHead showArrow={false}>
@@ -20,7 +137,20 @@ export function AlertingForm(props: Props) {
           </AccordionHead>
           <AccordionBody>
             <div>
-              <input className="border" />
+              <Input
+                type="text"
+                value={recipientInput}
+                onChange={(value) => {
+                  setRecipientInput(String(value));
+                  const recipientList = String(value)
+                    .split(/[, ]/)
+                    .map(r => r.trim())
+                    .filter(r => r.length > 0);
+                  setRecipients(recipientList);
+                }}
+                placeholder="Enter recipient(s)"
+                className="w-full"
+              />
             </div>
           </AccordionBody>
         </AccordionItem>
@@ -30,6 +160,7 @@ export function AlertingForm(props: Props) {
           </AccordionHead>
           <AccordionBody>
             <TextEditor
+              key={props.insight.id}
               editorState={editorState}
               setEditorState={setEditorState}
               showToolbar={true}
@@ -38,6 +169,10 @@ export function AlertingForm(props: Props) {
           </AccordionBody>
         </AccordionItem>
       </Accordion>
+      <div className="flex justify-end mt-2">
+        <Button label="Delete" variantType="secondary" variant="outline" onClick={handleDelete} />
+        <Button label="Save" variantType="primary" className="ml-2" onClick={handleSave} />
+      </div>
     </div>
   );
-}
+}
\ No newline at end of file
diff --git a/libs/shared/lib/insight-sharing/components/AddItem.tsx b/libs/shared/lib/insight-sharing/components/AddItem.tsx
index 771ab6bcc..78ce7c378 100644
--- a/libs/shared/lib/insight-sharing/components/AddItem.tsx
+++ b/libs/shared/lib/insight-sharing/components/AddItem.tsx
@@ -1,18 +1,71 @@
 import React, { useState } from 'react';
-import { Input } from '../../components';
+import { Input, Button } from '../../components';
 import { MonitorType } from './Sidebar';
+import { useAppDispatch, useSessionCache } from '../../data-access';
+import { addInsight } from '../../data-access/store/insightSharingSlice';
+import { createAlert, createReport } from '../../data-access/api/apiService';
+import { v4 as uuidv4 } from 'uuid';
 
 type Props = {
   category: MonitorType;
+  setAdding: (val: boolean) => void;
+  setActive: (val: string) => void;
+  setActiveCategory: (val: MonitorType | undefined) => void;
 };
 
 export function AddItem(props: Props) {
-  const [value, setValue] = useState<string>('');
+  const [name, setName] = useState<string>('');
+  const [description, setDescription] = useState<string>('');
+  const [loading, setLoading] = useState(false);
+  const dispatch = useAppDispatch();
+  const session = useSessionCache();
+
+  const handleSave = async () => {
+    if (!name.trim()) {
+      alert('Please enter a name.');
+      return;
+    }
+    if (!description.trim()) {
+      alert('Please enter a description.');
+      return;
+    }
+
+    const newInsight = {
+      name,
+      description,
+      recipients: [],
+      template: '',
+      frequency: props.category === 'report' ? 'Daily' : undefined,
+      save_state_id: session.currentSaveState,
+      type: props.category,
+    };
+    setLoading(true);
+    try {
+      let savedInsight;
+      if (props.category === 'report') {
+        savedInsight = await createReport(newInsight);
+      } else {
+        savedInsight = await createAlert(newInsight)
+      }
+      
+      dispatch(addInsight(savedInsight));
+      props.setActive(savedInsight.id);
+      props.setActiveCategory(props.category);
+      props.setAdding(false);
+    } catch (error) {
+      console.error('Failed to create insight:', error);
+      alert('Failed to create new ' + props.category);
+    } finally {
+      setLoading(false);
+    }
+  };
 
   return (
     <div>
       <span className="text-lg text-secondary-600 font-bold mb-4">Add a new {props.category}ing service</span>
-      <Input type="text" label="Name" value={value} onChange={setValue} />
+      <Input type="text" label="Name" value={name} onChange={setName} className="mb-2" />
+      <Input type="text" label="Description" value={description} onChange={setDescription} className="mb-2" />
+      <Button label="Save" onClick={handleSave} disabled={!name || !description} className="mt-2" />
     </div>
   );
 }
diff --git a/libs/shared/lib/insight-sharing/components/Sidebar.tsx b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
index d8ebcfe1c..d4f5c15f6 100644
--- a/libs/shared/lib/insight-sharing/components/Sidebar.tsx
+++ b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
@@ -1,18 +1,41 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 import { Button } from '../../components';
 import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
+import { useAppDispatch, useAppSelector } from '../../data-access';
+import { setInsights } from '../../data-access/store/insightSharingSlice';
+import { getReports } from '../../data-access/api/apiService';
+import { useSessionCache } from '../../data-access';
 
 export type MonitorType = 'report' | 'alert';
 
 type SidebarProps = {
-  reports: string[];
-  alerts: string[];
   changeActive: (category: MonitorType, val: string) => void;
   setAdding: (val: boolean) => void;
   setActiveCategory: (val: MonitorType | undefined) => void;
 };
 
 export function Sidebar(props: SidebarProps) {
+  const dispatch = useAppDispatch();
+  const session = useSessionCache();
+  const reports = useAppSelector((state) => state.insightSharing.reports);
+  const alerts = useAppSelector((state) => state.insightSharing.alerts);
+
+  useEffect(() => {
+    async function fetchReports() {
+      try {
+        if (session.currentSaveState && session.currentSaveState !== '') {
+          const fetchedReports = await getReports(session.currentSaveState);
+          dispatch(setInsights(fetchedReports));
+        }
+      } catch (error) {
+        console.error('Failed to fetch reports:', error);
+      }
+    }
+    
+    fetchReports();
+  }, [session.currentSaveState, dispatch]);
+
+
   return (
     <div className="w-1/4 border-r overflow-auto flex flex-col h-full">
       <span className="text-lg text-secondary-700 font-semibold px-2 py-4">Insight Sharing</span>
@@ -41,16 +64,16 @@ export function Sidebar(props: SidebarProps) {
           </AccordionHead>
           <AccordionBody className="ml-0">
             <ul className="space-y-2">
-              {props.reports.map((name, index) => (
+              {reports.map((report) => (
                 <li
-                  key={index}
+                  key={report.id}
                   className="cursor-pointer p-2 hover:bg-secondary-50"
                   onClick={() => {
-                    props.changeActive('report', name);
+                    props.changeActive('report', report.id);
                     props.setAdding(false);
                   }}
                 >
-                  {name}
+                  {report.name}
                 </li>
               ))}
             </ul>
@@ -80,16 +103,16 @@ export function Sidebar(props: SidebarProps) {
           </AccordionHead>
           <AccordionBody className="ml-0">
             <ul className="space-y-2">
-              {props.alerts.map((name, index) => (
+              {alerts.map((alert) => (
                 <li
-                  key={index}
+                  key={alert.id}
                   className="cursor-pointer p-2 hover:bg-secondary-50"
                   onClick={() => {
-                    props.changeActive('alert', name);
+                    props.changeActive('alert', alert.id);
                     props.setAdding(false);
                   }}
                 >
-                  {name}
+                  {alert.name}
                 </li>
               ))}
             </ul>
diff --git a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
index 9b2645b91..ec1284d4a 100644
--- a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
+++ b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
@@ -1,57 +1,203 @@
-import React, { useState } from 'react';
-import { Input, LoadingSpinner } from '../../components';
+import React, { useState, useEffect } from 'react';
+import { Button, Input, LoadingSpinner } from '../../components';
 import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
 import { TextEditor } from '../../components/textEditor';
+import { Insight, updateInsight, deleteInsight } from '../../data-access/store/insightSharingSlice';
+import { useAppDispatch, useSessionCache } from '../../data-access';
+import { createReport, updateReport, deleteReport } from '../../data-access/api/apiService';
+import { MonitorType } from '../components/Sidebar';
 import { EditorState } from 'lexical';
 
 type Props = {
-  activeTemplate: string;
+  insight: Insight;
+  setActiveCategory: (val: MonitorType | undefined) => void;
+  setActive: (val: string) => void;
+  handleDelete: () => void;
 };
 
 export function ReportingForm(props: Props) {
+  const dispatch = useAppDispatch();
+  const session = useSessionCache();
   const [loading, setLoading] = useState(false);
-  const [editorState, setEditorState] = useState<EditorState | undefined>(undefined);
+  const [name, setName] = useState(props.insight.name);
+  const [description, setDescription] = useState(props.insight.description);
+  const [recipients, setRecipients] = useState(props.insight.recipients || []);
+  const [recipientInput, setRecipientInput] = useState<string>('');
+  const [frequency, setFrequency] = useState(props.insight.frequency || 'Daily');
+  const [editorState, setEditorState] = useState<EditorState | null>(null);
+
+  useEffect(() => {
+    setName(props.insight.name);
+    setDescription(props.insight.description);
+    setRecipientInput(props.insight.recipients ? props.insight.recipients.join(', ') : '');
+    setRecipients(props.insight.recipients || []);
+    setFrequency(props.insight.frequency || 'Daily');
+    if (props.insight.template) {
+      try {
+        const parsedTemplate = JSON.parse(props.insight.template);
+        setEditorState(parsedTemplate);
+      } catch (e) {
+        setEditorState(null);
+        console.error('Failed to parse template:', e);
+      }
+    } else {
+      setEditorState(null);
+    }
+  }, [props.insight]);
+
+  const handleSetFrequency = (value: string | number) => {
+    setFrequency(value as string);
+  };
+
+  const handleSetName = (value: string | number) => {
+    setName(value as string);
+  };
+
+  const handleSetDescription = (value: string | number) => {
+    setDescription(value as string);
+  };
+
+  const handleSave = async () => {
+    if (!name || name.trim() === '') {
+      alert('Please enter a name for the report.');
+      return;
+    }
+    if (!description || description.trim() === '') {
+      alert('Please enter a description for the report.');
+      return;
+    }
+    if (!editorState) {
+      alert('Please enter content in the text editor before saving.');
+      return;
+    }
+
+    const reportData: Partial<Insight> = {
+      id: props.insight.id,
+      name,
+      description,
+      recipients,
+      frequency,
+      template: JSON.stringify(editorState), // Convert editorState to string
+      save_state_id: session.currentSaveState,
+      type: 'report',
+    };
+
+    setLoading(true);
+    try {
+      let savedReport;
+      if (props.insight.id) {
+        savedReport = await updateReport(props.insight.id, reportData);
+        dispatch(updateInsight(savedReport));
+      } else {
+        savedReport = await createReport(reportData);
+        dispatch(updateInsight(savedReport));
+      }
+      console.log('Report saved successfully');
+    } catch (error) {
+      console.error('Failed to save report:', error);
+      alert('Failed to save report.');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleDelete = async () => {
+    if (!props.insight.id) {
+      alert('Cannot delete a report without an ID.');
+      return;
+    }
+
+    setLoading(true);
+    try {
+      await deleteReport(props.insight.id);
+      dispatch(deleteInsight({ id: props.insight.id, type: props.insight.type }));
+      props.setActive('');
+      props.setActiveCategory(undefined);
+    } catch (error) {
+      console.error('Failed to delete report:', error);
+      alert('Failed to delete report.');
+    } finally {
+      setLoading(false);
+    }
+  };
 
   return loading ? (
     <LoadingSpinner />
   ) : (
-    props.activeTemplate && (
-      <div>
-        <span className="text-lg text-secondary-600 font-bold mb-4">Report ID: {props.activeTemplate}</span>
-        <Accordion defaultOpenAll={true} className="border-t divide-y">
-          <AccordionItem className="pt-2 pb-4">
-            <AccordionHead showArrow={false}>
-              <span className="font-semibold">Recipient(s)</span>
-            </AccordionHead>
-            <AccordionBody>
-              <div>
-                <input className="border" />
-              </div>
-            </AccordionBody>
-          </AccordionItem>
-          <AccordionItem className="pt-2 pb-4">
-            <AccordionHead showArrow={false}>
-              <span className="font-semibold">Repeat</span>
-            </AccordionHead>
-            <AccordionBody>
-              <Input label="Frequency" type="dropdown" value={'Daily'} onChange={() => {}} options={['Daily', 'Weekly']} className="mb-1" />
-            </AccordionBody>
-          </AccordionItem>
-          <AccordionItem className="pt-2 pb-4">
-            <AccordionHead showArrow={false}>
-              <span className="font-semibold">Email template</span>
-            </AccordionHead>
-            <AccordionBody>
-              <TextEditor
-                editorState={editorState}
-                setEditorState={setEditorState}
-                showToolbar={true}
-                placeholder="Start typing your report template..."
+    <div>
+      <span className="text-lg text-secondary-600 font-bold mb-4">Report ID: {props.insight.id}</span>
+      <Input
+        label="Name"
+        type="text"
+        value={name}
+        onChange={handleSetName}
+        className="mb-4"
+      />
+      <Input
+        label="Description"
+        type="text"
+        value={description}
+        onChange={handleSetDescription}
+        className="mb-4"
+      />
+      <Accordion defaultOpenAll={true} className="border-t divide-y">
+        <AccordionItem className="pt-2 pb-4">
+          <AccordionHead showArrow={false}>
+            <span className="font-semibold">Recipient(s)</span>
+          </AccordionHead>
+          <AccordionBody>
+            <div>
+              <Input
+                type="text"
+                value={recipientInput}
+                onChange={(value) => {
+                  setRecipientInput(String(value));
+                  const recipientList = String(value)
+                    .split(/[, ]/)
+                    .map(r => r.trim())
+                    .filter(r => r.length > 0);
+                  setRecipients(recipientList);
+                }}
+                placeholder="Enter recipient(s)"
+                className="w-full"
               />
-            </AccordionBody>
-          </AccordionItem>
-        </Accordion>
+            </div>
+          </AccordionBody>
+        </AccordionItem>
+        <AccordionItem className="pt-2 pb-4">
+          <AccordionHead showArrow={false}>
+            <span className="font-semibold">Repeat</span>
+          </AccordionHead>
+          <AccordionBody>
+            <Input
+              label="Frequency"
+              type="dropdown"
+              value={frequency}
+              onChange={handleSetFrequency}
+              options={['Daily', 'Weekly']}
+              className="mb-1"
+            />
+          </AccordionBody>
+        </AccordionItem>
+        <AccordionItem className="pt-2 pb-4">
+          <AccordionHead showArrow={false}>
+            <span className="font-semibold">Email Template</span>
+          </AccordionHead>
+          <AccordionBody>
+            <TextEditor
+              key={props.insight.id}
+              editorState={editorState}
+              setEditorState={setEditorState}
+              showToolbar={true}
+              placeholder="Start typing your report template..."
+            />
+          </AccordionBody>
+        </AccordionItem>
+      </Accordion>
+      <div className="flex justify-end mt-2">
+        <Button label="Delete" variantType="secondary" variant="outline" onClick={handleDelete} />
+        <Button label="Save" variantType="primary" className="ml-2" onClick={handleSave} />
       </div>
-    )
+    </div>
   );
 }
-- 
GitLab


From e0156142babef079e01249df6189e178297fab4d Mon Sep 17 00:00:00 2001
From: Samed <sbalcioglu@graphpolaris.com>
Date: Mon, 2 Dec 2024 11:48:21 +0100
Subject: [PATCH 2/9] feat: updated reportingform

---
 libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
index ec1284d4a..b73983f51 100644
--- a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
+++ b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
@@ -77,7 +77,7 @@ export function ReportingForm(props: Props) {
       description,
       recipients,
       frequency,
-      template: JSON.stringify(editorState), // Convert editorState to string
+      template: JSON.stringify(editorState),
       save_state_id: session.currentSaveState,
       type: 'report',
     };
-- 
GitLab


From 5541f407485ebbe57f399fa2640e6c2f194014ab Mon Sep 17 00:00:00 2001
From: Samed <sbalcioglu@graphpolaris.com>
Date: Tue, 3 Dec 2024 12:56:48 +0100
Subject: [PATCH 3/9] chore: updates

---
 libs/shared/lib/data-access/api/apiService.ts | 106 ------------------
 libs/shared/lib/data-access/broker/types.ts   |   2 +-
 .../data-access/broker/wsInsightSharing.ts    |  21 ++--
 .../data-access/store/insightSharingSlice.ts  |  31 ++---
 .../lib/insight-sharing/SettingsPanel.tsx     |   4 +-
 .../insight-sharing/alerting/AlertingForm.tsx |  23 ++--
 .../insight-sharing/components/AddItem.tsx    |  37 +++---
 .../insight-sharing/components/Sidebar.tsx    |  18 +--
 .../reporting/ReportingForm.tsx               |  75 +++++++------
 9 files changed, 114 insertions(+), 203 deletions(-)
 delete mode 100644 libs/shared/lib/data-access/api/apiService.ts

diff --git a/libs/shared/lib/data-access/api/apiService.ts b/libs/shared/lib/data-access/api/apiService.ts
deleted file mode 100644
index a9ea11cfe..000000000
--- a/libs/shared/lib/data-access/api/apiService.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import axios from 'axios';
-import { Insight } from '../store/insightSharingSlice';
-
-const API_BASE_URL = 'http://localhost:8000/api';
-
-const axiosInstance = axios.create({
-    baseURL: API_BASE_URL,
-    headers: {
-      'Content-Type': 'application/json',
-    }
-  });
-
-  export async function createReport(reportData: Partial<Insight>) {
-    console.log('Creating report with data:', reportData);
-    try {
-      const dataToSend = { ...reportData };
-      delete dataToSend.id;
-      delete dataToSend.type;
-
-      const response = await axiosInstance.post('/report/', dataToSend);
-      console.log('Report creation response:', response);
-
-      const report = { ...response.data, type: 'report' };
-
-      return report;
-    } catch (error) {
-      console.error('Error creating report:', error);
-      if (axios.isAxiosError(error)) {
-        console.error('Response data:', error.response?.data);
-        console.error('Request URL:', error.config?.url);
-      }
-      throw error;
-    }
-  }
-
-  export async function getReports(saveStateId: string) {
-    console.log('Fetching reports for saveStateId:', saveStateId);
-    try {
-      const response = await axiosInstance.get(`/report/save_state/${saveStateId}/`);
-      console.log('Reports fetch response:', response);
-      const reports = response.data.map((report: Insight) => ({
-        ...report,
-        type: 'report',
-      }));
-  
-      return reports;
-    } catch (error) {
-      console.error('Error fetching reports:', error);
-      if (axios.isAxiosError(error)) {
-        console.error('Response data:', error.response?.data);
-        console.error('Request URL:', error.config?.url);
-      }
-      return [];
-    }
-  }
-
-  export async function updateReport(reportId: string, reportData: Partial<Insight>) {
-    try {
-      console.log('Updating report with data:', reportData);
-      const response = await axiosInstance.put(`/report/${reportId}/`, reportData);
-      return response.data;
-    } catch (error) {
-      console.error('Error updating report:', error);
-      throw error;
-    }
-  }
-
-    export async function deleteReport(reportId: string) {
-    try {
-        const response = await axiosInstance.delete(`report/${reportId}/`);
-        return response.data;
-    } catch (error) {
-        console.error('Error deleting report:', error);
-        throw error;
-    }
-    }
-
-    export async function createAlert(alertData: any) {
-        try {
-        const response = await axiosInstance.post('alert/', alertData);
-        return response.data;
-        } catch (error) {
-        console.error('Error creating alert:', error);
-        throw error;
-        }
-    }
-    
-    export async function updateAlert(alertId: string, alertData: any) {
-        try {
-        const response = await axiosInstance.put(`alert/${alertId}`, alertData);
-        return response.data;
-        } catch (error) {
-        console.error('Error updating alert:', error);
-        throw error;
-        }
-    }
-    
-    export async function deleteAlert(alertId: string) {
-        try {
-        const response = await axiosInstance.delete(`alert/${alertId}`);
-        return response.data;
-        } catch (error) {
-        console.error('Error deleting alert:', error);
-        throw error;
-        }
-    }
diff --git a/libs/shared/lib/data-access/broker/types.ts b/libs/shared/lib/data-access/broker/types.ts
index 462762e18..8d6decc5a 100644
--- a/libs/shared/lib/data-access/broker/types.ts
+++ b/libs/shared/lib/data-access/broker/types.ts
@@ -19,7 +19,7 @@ type QueryOrchestratorMessage = {
   queryID: string;
 };
 
-export type keyTypeI = 'broadcastState' | 'dbConnection' | 'schema' | 'query' | 'state' | 'user' | 'insight'| 'report' | 'alert';
+export type keyTypeI = 'broadcastState' | 'dbConnection' | 'schema' | 'query' | 'state' | 'user' | 'insight';
 export type subKeyTypeI =
   // Crud
   | 'create'
diff --git a/libs/shared/lib/data-access/broker/wsInsightSharing.ts b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
index 98f18b7ec..b391d49f1 100644
--- a/libs/shared/lib/data-access/broker/wsInsightSharing.ts
+++ b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
@@ -1,7 +1,7 @@
 import { Broker } from './broker';
-import { Insight, InsightType } from '../store/insightSharingSlice';
+import { InsightRequest, InsightResponse, InsightType } from '../store/insightSharingSlice';
 
-type GetInsightsResponse = (data: { reports: Insight[]; alerts: Insight[] }, status: string) => void;
+type GetInsightsResponse = (data: { reports: InsightResponse[]; alerts: InsightResponse[] }, status: string) => void;
 export function wsGetInsights(callback?: GetInsightsResponse) {
   const internalCallback: GetInsightsResponse = (data, status) => {
     if (callback) callback(data, status);
@@ -16,15 +16,16 @@ export function wsGetInsights(callback?: GetInsightsResponse) {
   );
 }
 
-export function wsCreateInsight(insight: Insight, callback?: Function) {
+export function wsCreateInsight(insight: InsightRequest, callback?: Function) {
   const messageKey = insight.type === 'report' ? 'report' : 'alert';
   Broker.instance().sendMessage(
     {
-      key: messageKey,
+      key: 'insight',
       subKey: 'create',
-      body: insight,
+      body: JSON.stringify(insight),
     },
     (data: any, status: string) => {
+      debugger;
       if (status === 'Bad Request') {
         console.error('Failed to create insight:', data);
       }
@@ -33,13 +34,13 @@ export function wsCreateInsight(insight: Insight, callback?: Function) {
   );
 }
 
-export function wsUpdateInsight(insight: Insight, callback?: Function) {
+export function wsUpdateInsight(insight: InsightRequest, callback?: Function) {
   const messageKey = insight.type === 'report' ? 'report' : 'alert';
   Broker.instance().sendMessage(
     {
-      key: messageKey,
+      key: 'insight',
       subKey: 'update',
-      body: insight,
+      body: JSON.stringify(insight),
     },
     (data: any, status: string) => {
       if (status === 'Bad Request') {
@@ -54,9 +55,9 @@ export function wsDeleteInsight(id: string, type: InsightType, callback?: Functi
   const messageKey = type === 'report' ? 'report' : 'alert';
   Broker.instance().sendMessage(
     {
-      key: messageKey,
+      key: 'insight',
       subKey: 'delete',
-      body: { id },
+      body: JSON.stringify({ id, type }),
     },
     (data: any, status: string) => {
       if (status === 'Bad Request') {
diff --git a/libs/shared/lib/data-access/store/insightSharingSlice.ts b/libs/shared/lib/data-access/store/insightSharingSlice.ts
index c2859b576..2c06055bd 100644
--- a/libs/shared/lib/data-access/store/insightSharingSlice.ts
+++ b/libs/shared/lib/data-access/store/insightSharingSlice.ts
@@ -3,23 +3,26 @@ import { RootState } from '../store';
 
 export type InsightType = 'report' | 'alert';
 
-export interface Insight {
-  id: string;
+export type InsightRequest = {
   name: string;
   description: string;
   recipients: string[];
   frequency: string;
   template: string;
-  save_state_id: string;
-  createdAt: string;
-  updatedAt: string;
+  saveStateId: string;
   type: 'report' | 'alert';
 }
 
-interface InsightState {
-  insights: Insight[];
-  reports: Insight[];
-  alerts: Insight[];
+export type InsightResponse = {
+  id: string;
+  createdAt: string;
+  updatedAt: string;
+} & InsightRequest;
+
+type InsightState = {
+  insights: InsightResponse[];
+  reports: InsightResponse[];
+  alerts: InsightResponse[];
 }
 
 const initialState: InsightState = {
@@ -32,19 +35,19 @@ const insightSharingSlice = createSlice({
   name: 'insightSharing',
   initialState,
   reducers: {
-    setInsights(state, action: PayloadAction<Insight[] | { reports: Insight[] }>) {
+    setInsights(state, action: PayloadAction<InsightResponse[] | { reports: InsightResponse[] }>) {
       const insights = Array.isArray(action.payload) ? action.payload : action.payload.reports;
       state.reports = insights.filter((insight) => insight.type === 'report');
       state.alerts = insights.filter((insight) => insight.type === 'alert');
     },
-    addInsight(state, action: PayloadAction<Insight>) {
+    addInsight(state, action: PayloadAction<InsightResponse>) {
       if (action.payload.type === 'report') {
         state.reports.push(action.payload);
       } else {
         state.alerts.push(action.payload);
       }
     },
-    updateInsight(state, action: PayloadAction<Insight>) {
+    updateInsight(state, action: PayloadAction<InsightResponse>) {
       const insights = action.payload.type === 'report' ? state.reports : state.alerts;
       const index = insights.findIndex((i) => i.id === action.payload.id);
       if (index !== -1) {
@@ -64,7 +67,7 @@ const insightSharingSlice = createSlice({
 });
 
 export const { setInsights, addInsight, updateInsight, deleteInsight } = insightSharingSlice.actions;
-export const selectReports = (state: RootState): Insight[] => state.insightSharing.reports;
-export const selectAlerts = (state: RootState): Insight[] => state.insightSharing.alerts;
+export const selectReports = (state: RootState): InsightResponse[] => state.insightSharing.reports;
+export const selectAlerts = (state: RootState): InsightResponse[] => state.insightSharing.alerts;
 
 export default insightSharingSlice.reducer;
diff --git a/libs/shared/lib/insight-sharing/SettingsPanel.tsx b/libs/shared/lib/insight-sharing/SettingsPanel.tsx
index 54c65d266..ba6d02f74 100644
--- a/libs/shared/lib/insight-sharing/SettingsPanel.tsx
+++ b/libs/shared/lib/insight-sharing/SettingsPanel.tsx
@@ -9,7 +9,7 @@ import {
   deleteInsight,
   selectReports,
   selectAlerts,
-  Insight,
+  InsightResponse,
 } from '../data-access/store/insightSharingSlice';
 import { wsDeleteInsight } from '../data-access/broker/wsInsightSharing';
 
@@ -27,7 +27,7 @@ export function SettingsPanel(props: Props) {
   const reports = useAppSelector(selectReports);
   const alerts = useAppSelector(selectAlerts);
 
-  const [currentInsight, setCurrentInsight] = useState<Insight | null>(null);
+  const [currentInsight, setCurrentInsight] = useState<InsightResponse | null>(null);
 
   useEffect(() => {
     if (props.activeCategory && props.active) {
diff --git a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
index 1870a1ffd..64ea13c85 100644
--- a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
+++ b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
@@ -1,15 +1,14 @@
 import React, { useEffect, useState } from 'react';
 import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '../../components/accordion';
 import { TextEditor } from '../../components/textEditor';
-import { Insight, updateInsight, deleteInsight } from '../../data-access/store/insightSharingSlice';
+import { InsightResponse, updateInsight, deleteInsight } from '../../data-access/store/insightSharingSlice';
 import { useAppDispatch, useSessionCache } from '../../data-access';
 import { Button, Input, LoadingSpinner } from '../../components';
 import { EditorState } from 'lexical';
-import { createAlert, updateAlert, deleteAlert } from '../../data-access/api/apiService';
 import { MonitorType } from '../components/Sidebar';
 
 type Props = {
-  insight: Insight;
+  insight: InsightResponse;
   setActiveCategory?: (val: MonitorType | undefined) => void;
   setActive?: (val: string) => void;
   handleDelete: () => void;
@@ -71,13 +70,14 @@ export function AlertingForm(props: Props) {
     setLoading(true);
     try {
       let savedAlert;
-      if (props.insight.id) {
-        savedAlert = await updateAlert(props.insight.id, alertData);
-        dispatch(updateInsight(savedAlert));
-      } else {
-        savedAlert = await createAlert(alertData);
-        dispatch(updateInsight(savedAlert));
-      }
+      debugger;
+      // if (props.insight.id) {
+      //   savedAlert = await updateAlert(props.insight.id, alertData);
+      //   dispatch(updateInsight(savedAlert));
+      // } else {
+      //   savedAlert = await createAlert(alertData);
+      //   dispatch(updateInsight(savedAlert));
+      // }
       console.log('Alert saved successfully');
     } catch (error) {
       console.error('Failed to save alert:', error);
@@ -95,7 +95,8 @@ export function AlertingForm(props: Props) {
 
     setLoading(true);
     try {
-      await deleteAlert(props.insight.id);
+      debugger;
+      // await deleteAlert(props.insight.id);
       dispatch(deleteInsight({ id: props.insight.id, type: props.insight.type }));
       if (props.setActive && props.setActiveCategory) {
         props.setActive('');
diff --git a/libs/shared/lib/insight-sharing/components/AddItem.tsx b/libs/shared/lib/insight-sharing/components/AddItem.tsx
index 78ce7c378..6db91c0d8 100644
--- a/libs/shared/lib/insight-sharing/components/AddItem.tsx
+++ b/libs/shared/lib/insight-sharing/components/AddItem.tsx
@@ -3,7 +3,6 @@ import { Input, Button } from '../../components';
 import { MonitorType } from './Sidebar';
 import { useAppDispatch, useSessionCache } from '../../data-access';
 import { addInsight } from '../../data-access/store/insightSharingSlice';
-import { createAlert, createReport } from '../../data-access/api/apiService';
 import { v4 as uuidv4 } from 'uuid';
 
 type Props = {
@@ -40,24 +39,26 @@ export function AddItem(props: Props) {
       type: props.category,
     };
     setLoading(true);
-    try {
-      let savedInsight;
-      if (props.category === 'report') {
-        savedInsight = await createReport(newInsight);
-      } else {
-        savedInsight = await createAlert(newInsight)
-      }
+    
+    debugger;
+    // try {
+    //   let savedInsight;
+    //   if (props.category === 'report') {
+    //     savedInsight = await createInsih(newInsight);
+    //   } else {
+    //     savedInsight = await createAlert(newInsight)
+    //   }
       
-      dispatch(addInsight(savedInsight));
-      props.setActive(savedInsight.id);
-      props.setActiveCategory(props.category);
-      props.setAdding(false);
-    } catch (error) {
-      console.error('Failed to create insight:', error);
-      alert('Failed to create new ' + props.category);
-    } finally {
-      setLoading(false);
-    }
+    //   dispatch(addInsight(savedInsight));
+    //   props.setActive(savedInsight.id);
+    //   props.setActiveCategory(props.category);
+    //   props.setAdding(false);
+    // } catch (error) {
+    //   console.error('Failed to create insight:', error);
+    //   alert('Failed to create new ' + props.category);
+    // } finally {
+    //   setLoading(false);
+    // }
   };
 
   return (
diff --git a/libs/shared/lib/insight-sharing/components/Sidebar.tsx b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
index d4f5c15f6..059bbde9e 100644
--- a/libs/shared/lib/insight-sharing/components/Sidebar.tsx
+++ b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
@@ -3,7 +3,6 @@ import { Button } from '../../components';
 import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
 import { useAppDispatch, useAppSelector } from '../../data-access';
 import { setInsights } from '../../data-access/store/insightSharingSlice';
-import { getReports } from '../../data-access/api/apiService';
 import { useSessionCache } from '../../data-access';
 
 export type MonitorType = 'report' | 'alert';
@@ -22,14 +21,15 @@ export function Sidebar(props: SidebarProps) {
 
   useEffect(() => {
     async function fetchReports() {
-      try {
-        if (session.currentSaveState && session.currentSaveState !== '') {
-          const fetchedReports = await getReports(session.currentSaveState);
-          dispatch(setInsights(fetchedReports));
-        }
-      } catch (error) {
-        console.error('Failed to fetch reports:', error);
-      }
+      debugger;
+      // try {
+      //   if (session.currentSaveState && session.currentSaveState !== '') {
+      //     const fetchedReports = await getReports(session.currentSaveState);
+      //     dispatch(setInsights(fetchedReports));
+      //   }
+      // } catch (error) {
+      //   console.error('Failed to fetch reports:', error);
+      // }
     }
     
     fetchReports();
diff --git a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
index b73983f51..e3df37f75 100644
--- a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
+++ b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
@@ -2,14 +2,14 @@ import React, { useState, useEffect } from 'react';
 import { Button, Input, LoadingSpinner } from '../../components';
 import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
 import { TextEditor } from '../../components/textEditor';
-import { Insight, updateInsight, deleteInsight } from '../../data-access/store/insightSharingSlice';
+import { InsightResponse, updateInsight, deleteInsight, InsightRequest } from '../../data-access/store/insightSharingSlice';
 import { useAppDispatch, useSessionCache } from '../../data-access';
-import { createReport, updateReport, deleteReport } from '../../data-access/api/apiService';
 import { MonitorType } from '../components/Sidebar';
 import { EditorState } from 'lexical';
+import { wsCreateInsight, wsDeleteInsight, wsUpdateInsight } from '../../data-access/broker/wsInsightSharing';
 
 type Props = {
-  insight: Insight;
+  insight: InsightResponse;
   setActiveCategory: (val: MonitorType | undefined) => void;
   setActive: (val: string) => void;
   handleDelete: () => void;
@@ -70,35 +70,45 @@ export function ReportingForm(props: Props) {
       alert('Please enter content in the text editor before saving.');
       return;
     }
+    if(!session.currentSaveState){
+      console.warn("No active save state found - this might affect report creation/updating");
+    }
 
-    const reportData: Partial<Insight> = {
-      id: props.insight.id,
+    const reportData: InsightRequest = {
       name,
       description,
       recipients,
       frequency,
       template: JSON.stringify(editorState),
-      save_state_id: session.currentSaveState,
+      saveStateId: session.currentSaveState || "",
       type: 'report',
     };
 
     setLoading(true);
-    try {
-      let savedReport;
-      if (props.insight.id) {
-        savedReport = await updateReport(props.insight.id, reportData);
-        dispatch(updateInsight(savedReport));
-      } else {
-        savedReport = await createReport(reportData);
-        dispatch(updateInsight(savedReport));
-      }
-      console.log('Report saved successfully');
-    } catch (error) {
-      console.error('Failed to save report:', error);
-      alert('Failed to save report.');
-    } finally {
-      setLoading(false);
-    }
+    // if (props.insight.id) {
+    //   wsUpdateInsight(reportData, (data: any, status: string) => {
+    //     setLoading(false);
+    //     if (status === 'success') {
+    //       dispatch(updateInsight(data));
+    //       console.log('Report updated successfully');
+    //     } else {
+    //       console.error('Failed to update report:', data);
+    //       alert('Failed to update report.');
+    //     }
+    //   });
+    // } else {
+      wsCreateInsight(reportData, (data: any, status: string) => {
+        debugger;
+        setLoading(false);
+        if (status === 'success') {
+          dispatch(updateInsight(data));
+          console.log('Report created successfully');
+        } else {
+          console.error('Failed to create report:', data);
+          alert('Failed to create report.');
+        }
+      });
+    // }
   };
 
   const handleDelete = async () => {
@@ -108,17 +118,18 @@ export function ReportingForm(props: Props) {
     }
 
     setLoading(true);
-    try {
-      await deleteReport(props.insight.id);
-      dispatch(deleteInsight({ id: props.insight.id, type: props.insight.type }));
-      props.setActive('');
-      props.setActiveCategory(undefined);
-    } catch (error) {
-      console.error('Failed to delete report:', error);
-      alert('Failed to delete report.');
-    } finally {
+    wsDeleteInsight(props.insight.id, props.insight.type, (data: any, status: string) => {
       setLoading(false);
-    }
+      if (status === 'success') {
+        dispatch(deleteInsight({ id: props.insight.id, type: props.insight.type }));
+        props.setActive('');
+        props.setActiveCategory(undefined);
+        console.log('Report deleted successfully');
+      } else {
+        console.error('Failed to delete report:', data);
+        alert('Failed to delete report.');
+      }
+    });
   };
 
   return loading ? (
-- 
GitLab


From 99b7c11163e6e2fb5fffb0fc0bb7ea71e0684eaa Mon Sep 17 00:00:00 2001
From: Samed <sbalcioglu@graphpolaris.com>
Date: Tue, 3 Dec 2024 16:25:11 +0100
Subject: [PATCH 4/9] feat: frontend receives from ws

---
 libs/shared/lib/data-access/broker/broker.tsx |  6 +-
 .../data-access/broker/wsInsightSharing.ts    | 33 ++++++--
 .../data-access/store/insightSharingSlice.ts  | 17 ++++-
 .../insight-sharing/alerting/AlertingForm.tsx | 75 ++++++++++---------
 .../insight-sharing/components/AddItem.tsx    | 41 ++++------
 .../insight-sharing/components/Sidebar.tsx    | 15 ++--
 .../reporting/ReportingForm.tsx               | 37 +++++----
 7 files changed, 123 insertions(+), 101 deletions(-)

diff --git a/libs/shared/lib/data-access/broker/broker.tsx b/libs/shared/lib/data-access/broker/broker.tsx
index ff09fb28a..41cfe6f8e 100644
--- a/libs/shared/lib/data-access/broker/broker.tsx
+++ b/libs/shared/lib/data-access/broker/broker.tsx
@@ -200,9 +200,9 @@ export class Broker {
     }
 
     fullMessage.sessionID = this.authHeader?.sessionID ?? '';
-    //if (message.body && typeof message.body !== 'string') {
-     // fullMessage.body = JSON.stringify(message.body);
-    //}
+    if (message.body && typeof message.body !== 'string') {
+      fullMessage.body = JSON.stringify(message.body);
+    }
 
     if (this.webSocket && this.connected && this.webSocket.readyState === 1) this.webSocket.send(JSON.stringify(fullMessage));
     else
diff --git a/libs/shared/lib/data-access/broker/wsInsightSharing.ts b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
index b391d49f1..b9432157e 100644
--- a/libs/shared/lib/data-access/broker/wsInsightSharing.ts
+++ b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
@@ -2,7 +2,7 @@ import { Broker } from './broker';
 import { InsightRequest, InsightResponse, InsightType } from '../store/insightSharingSlice';
 
 type GetInsightsResponse = (data: { reports: InsightResponse[]; alerts: InsightResponse[] }, status: string) => void;
-export function wsGetInsights(callback?: GetInsightsResponse) {
+export function wsGetInsights(saveStateId: string, callback?: GetInsightsResponse) {
   const internalCallback: GetInsightsResponse = (data, status) => {
     if (callback) callback(data, status);
   };
@@ -11,13 +11,13 @@ export function wsGetInsights(callback?: GetInsightsResponse) {
     {
       key: 'insight',
       subKey: 'getAll',
+      body: JSON.stringify({saveStateId: saveStateId})
     },
     internalCallback
   );
 }
 
 export function wsCreateInsight(insight: InsightRequest, callback?: Function) {
-  const messageKey = insight.type === 'report' ? 'report' : 'alert';
   Broker.instance().sendMessage(
     {
       key: 'insight',
@@ -25,22 +25,39 @@ export function wsCreateInsight(insight: InsightRequest, callback?: Function) {
       body: JSON.stringify(insight),
     },
     (data: any, status: string) => {
-      debugger;
       if (status === 'Bad Request') {
         console.error('Failed to create insight:', data);
-      }
       if (callback) callback(data, status);
+      return;
+    }
+
+    if (!data || typeof data !== 'object') {
+      console.error('Invalid repsonse data', data)
+      if (callback) callback(null, 'error');
+      return;
+    }
+
+    if (!data.type || !data.id) {
+      console.error('Missing fields in response', data)
+      if (callback) callback(null, 'error');
+      return;
+    }
+
+    if (callback) callback(data,status);
     }
   );
 }
 
-export function wsUpdateInsight(insight: InsightRequest, callback?: Function) {
-  const messageKey = insight.type === 'report' ? 'report' : 'alert';
+export function wsUpdateInsight( 
+  id: string, 
+  insight: InsightRequest,
+  callback?: (data: any, status: string) => void
+) {
   Broker.instance().sendMessage(
     {
       key: 'insight',
       subKey: 'update',
-      body: JSON.stringify(insight),
+      body: JSON.stringify({id: id, insight: insight}),
     },
     (data: any, status: string) => {
       if (status === 'Bad Request') {
@@ -52,7 +69,7 @@ export function wsUpdateInsight(insight: InsightRequest, callback?: Function) {
 }
 
 export function wsDeleteInsight(id: string, type: InsightType, callback?: Function) {
-  const messageKey = type === 'report' ? 'report' : 'alert';
+
   Broker.instance().sendMessage(
     {
       key: 'insight',
diff --git a/libs/shared/lib/data-access/store/insightSharingSlice.ts b/libs/shared/lib/data-access/store/insightSharingSlice.ts
index 2c06055bd..d9485069d 100644
--- a/libs/shared/lib/data-access/store/insightSharingSlice.ts
+++ b/libs/shared/lib/data-access/store/insightSharingSlice.ts
@@ -40,10 +40,21 @@ const insightSharingSlice = createSlice({
       state.reports = insights.filter((insight) => insight.type === 'report');
       state.alerts = insights.filter((insight) => insight.type === 'alert');
     },
-    addInsight(state, action: PayloadAction<InsightResponse>) {
-      if (action.payload.type === 'report') {
+    addInsight: (state, action: PayloadAction<InsightResponse>) => {
+      if (!action.payload) {
+        console.error('Received null payload in addInsight');
+        return;
+      }
+
+      const type = action.payload.type;
+      if (!type) {
+        console.error('Insight type is missing in payload', action.payload);
+        return;
+      }
+
+      if (type === 'report') {
         state.reports.push(action.payload);
-      } else {
+      } else if (type === 'alert') {
         state.alerts.push(action.payload);
       }
     },
diff --git a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
index 64ea13c85..0b53289b6 100644
--- a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
+++ b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
@@ -1,11 +1,12 @@
 import React, { useEffect, useState } from 'react';
 import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '../../components/accordion';
 import { TextEditor } from '../../components/textEditor';
-import { InsightResponse, updateInsight, deleteInsight } from '../../data-access/store/insightSharingSlice';
+import { InsightResponse, updateInsight, deleteInsight, addInsight, InsightRequest } from '../../data-access/store/insightSharingSlice';
 import { useAppDispatch, useSessionCache } from '../../data-access';
 import { Button, Input, LoadingSpinner } from '../../components';
 import { EditorState } from 'lexical';
 import { MonitorType } from '../components/Sidebar';
+import { wsUpdateInsight, wsCreateInsight, wsDeleteInsight } from '../../data-access/broker/wsInsightSharing';
 
 type Props = {
   insight: InsightResponse;
@@ -56,34 +57,42 @@ export function AlertingForm(props: Props) {
       return;
     }
 
-    const alertData = {
-      id: props.insight.id,
+    const alertData : InsightRequest = {
       name,
       description,
       recipients,
       template: JSON.stringify(editorState),
-      save_state_id: session.currentSaveState || '',
+      saveStateId: session.currentSaveState || '',
       type: 'alert' as const,
       frequency: ''
     };
 
     setLoading(true);
-    try {
-      let savedAlert;
-      debugger;
-      // if (props.insight.id) {
-      //   savedAlert = await updateAlert(props.insight.id, alertData);
-      //   dispatch(updateInsight(savedAlert));
-      // } else {
-      //   savedAlert = await createAlert(alertData);
-      //   dispatch(updateInsight(savedAlert));
-      // }
-      console.log('Alert saved successfully');
-    } catch (error) {
-      console.error('Failed to save alert:', error);
-      alert('Failed to save alert.');
-    } finally {
-      setLoading(false);
+    
+    if (props.insight.id) {
+      wsUpdateInsight(props.insight.id, alertData, (data: any, status: string) => {
+        setLoading(false);
+        if (status === 'success') {
+          dispatch(updateInsight(data));
+          console.log('Alert updated successfully');
+        } else {
+          console.error('Failed to update alert:', data);
+          alert('Failed to update alert.');
+        }
+      });
+    } else {
+      wsCreateInsight(alertData, (data: any, status: string) => {
+        setLoading(false);
+        if (status === 'success') {
+          dispatch(addInsight(data));
+          console.log('Alert created successfully');
+          if (props.setActive) props.setActive(data.id);
+          if (props.setActiveCategory) props.setActiveCategory('alert');
+        } else {
+          console.error('Failed to create alert:', data);
+          alert('Failed to create alert.');
+        }
+      });
     }
   };
 
@@ -94,20 +103,18 @@ export function AlertingForm(props: Props) {
     }
 
     setLoading(true);
-    try {
-      debugger;
-      // await deleteAlert(props.insight.id);
-      dispatch(deleteInsight({ id: props.insight.id, type: props.insight.type }));
-      if (props.setActive && props.setActiveCategory) {
-        props.setActive('');
-        props.setActiveCategory(undefined);
-      }
-    } catch (error) {
-      console.error('Failed to delete alert:', error);
-      alert('Failed to delete alert.');
-    } finally {
+    wsDeleteInsight(props.insight.id, 'alert', (data: any, status: string) => {
       setLoading(false);
-    }
+      if (status === 'success') {
+        dispatch(deleteInsight({ id: props.insight.id, type: 'alert' }));
+        if (props.setActive) props.setActive('');
+        if (props.setActiveCategory) props.setActiveCategory(undefined);
+        console.log('Alert deleted successfully');
+      } else {
+        console.error('Failed to delete alert:', data);
+        alert('Failed to delete alert.');
+      }
+    });
   };
 
   if (loading) {
@@ -161,7 +168,7 @@ export function AlertingForm(props: Props) {
           </AccordionHead>
           <AccordionBody>
             <TextEditor
-              key={props.insight.id}
+              key={props.insight.id || 'new'}
               editorState={editorState}
               setEditorState={setEditorState}
               showToolbar={true}
diff --git a/libs/shared/lib/insight-sharing/components/AddItem.tsx b/libs/shared/lib/insight-sharing/components/AddItem.tsx
index 6db91c0d8..d58e3709b 100644
--- a/libs/shared/lib/insight-sharing/components/AddItem.tsx
+++ b/libs/shared/lib/insight-sharing/components/AddItem.tsx
@@ -4,6 +4,7 @@ import { MonitorType } from './Sidebar';
 import { useAppDispatch, useSessionCache } from '../../data-access';
 import { addInsight } from '../../data-access/store/insightSharingSlice';
 import { v4 as uuidv4 } from 'uuid';
+import { wsCreateInsight } from '../../data-access/broker/wsInsightSharing';
 
 type Props = {
   category: MonitorType;
@@ -24,41 +25,31 @@ export function AddItem(props: Props) {
       alert('Please enter a name.');
       return;
     }
-    if (!description.trim()) {
-      alert('Please enter a description.');
-      return;
-    }
 
     const newInsight = {
       name,
       description,
       recipients: [],
       template: '',
-      frequency: props.category === 'report' ? 'Daily' : undefined,
-      save_state_id: session.currentSaveState,
+      frequency: props.category === 'report' ? 'Daily' : '',
+      saveStateId: session.currentSaveState || '',
       type: props.category,
     };
     setLoading(true);
     
-    debugger;
-    // try {
-    //   let savedInsight;
-    //   if (props.category === 'report') {
-    //     savedInsight = await createInsih(newInsight);
-    //   } else {
-    //     savedInsight = await createAlert(newInsight)
-    //   }
-      
-    //   dispatch(addInsight(savedInsight));
-    //   props.setActive(savedInsight.id);
-    //   props.setActiveCategory(props.category);
-    //   props.setAdding(false);
-    // } catch (error) {
-    //   console.error('Failed to create insight:', error);
-    //   alert('Failed to create new ' + props.category);
-    // } finally {
-    //   setLoading(false);
-    // }
+    
+    wsCreateInsight(newInsight, (data: any, status: string) => {
+      setLoading(false);
+      if (status === 'success') {
+        dispatch(addInsight(data));
+        props.setActive(data.id);
+        props.setActiveCategory(props.category);
+        props.setAdding(false);
+      } else {
+        console.error('Failed to create insight:', data);
+        alert('Failed to create new ' + props.category);
+      }
+    });
   };
 
   return (
diff --git a/libs/shared/lib/insight-sharing/components/Sidebar.tsx b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
index 059bbde9e..a3b7bd841 100644
--- a/libs/shared/lib/insight-sharing/components/Sidebar.tsx
+++ b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
@@ -4,6 +4,7 @@ import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../co
 import { useAppDispatch, useAppSelector } from '../../data-access';
 import { setInsights } from '../../data-access/store/insightSharingSlice';
 import { useSessionCache } from '../../data-access';
+import { wsGetInsights } from '../../data-access/broker/wsInsightSharing';
 
 export type MonitorType = 'report' | 'alert';
 
@@ -21,15 +22,11 @@ export function Sidebar(props: SidebarProps) {
 
   useEffect(() => {
     async function fetchReports() {
-      debugger;
-      // try {
-      //   if (session.currentSaveState && session.currentSaveState !== '') {
-      //     const fetchedReports = await getReports(session.currentSaveState);
-      //     dispatch(setInsights(fetchedReports));
-      //   }
-      // } catch (error) {
-      //   console.error('Failed to fetch reports:', error);
-      // }
+      if (session.currentSaveState && session.currentSaveState !== '') {
+        wsGetInsights(session.currentSaveState, (data: any, status: string)=>{
+          dispatch(setInsights(data));
+        })
+      }
     }
     
     fetchReports();
diff --git a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
index e3df37f75..0bff6cbbb 100644
--- a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
+++ b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
@@ -70,9 +70,6 @@ export function ReportingForm(props: Props) {
       alert('Please enter content in the text editor before saving.');
       return;
     }
-    if(!session.currentSaveState){
-      console.warn("No active save state found - this might affect report creation/updating");
-    }
 
     const reportData: InsightRequest = {
       name,
@@ -81,34 +78,36 @@ export function ReportingForm(props: Props) {
       frequency,
       template: JSON.stringify(editorState),
       saveStateId: session.currentSaveState || "",
-      type: 'report',
+      type: 'report' as const,
     };
 
     setLoading(true);
-    // if (props.insight.id) {
-    //   wsUpdateInsight(reportData, (data: any, status: string) => {
-    //     setLoading(false);
-    //     if (status === 'success') {
-    //       dispatch(updateInsight(data));
-    //       console.log('Report updated successfully');
-    //     } else {
-    //       console.error('Failed to update report:', data);
-    //       alert('Failed to update report.');
-    //     }
-    //   });
-    // } else {
+    if (props.insight.id) {
+      wsUpdateInsight(props.insight.id, reportData, (data: any, status: string) => {
+        setLoading(false);
+        if (status === 'success') {
+          dispatch(updateInsight(data));
+          console.log('Report updated successfully');
+        } else {
+          console.error('Failed to update report:', data);
+          alert('Failed to update report.');
+        }
+      });
+    } else {
       wsCreateInsight(reportData, (data: any, status: string) => {
         debugger;
         setLoading(false);
         if (status === 'success') {
           dispatch(updateInsight(data));
           console.log('Report created successfully');
+          if (props.setActive) props.setActive(data.id);
+          if (props.setActiveCategory) props.setActiveCategory('report');
         } else {
           console.error('Failed to create report:', data);
           alert('Failed to create report.');
         }
       });
-    // }
+     }
   };
 
   const handleDelete = async () => {
@@ -196,7 +195,7 @@ export function ReportingForm(props: Props) {
           </AccordionHead>
           <AccordionBody>
             <TextEditor
-              key={props.insight.id}
+              key={props.insight.id || 'new'}
               editorState={editorState}
               setEditorState={setEditorState}
               showToolbar={true}
@@ -211,4 +210,4 @@ export function ReportingForm(props: Props) {
       </div>
     </div>
   );
-}
+}
\ No newline at end of file
-- 
GitLab


From 3eafc3ced8f09d5e03b49ff06efc696417ed02c6 Mon Sep 17 00:00:00 2001
From: Leonardo <leomilho@gmail.com>
Date: Tue, 3 Dec 2024 17:10:49 +0100
Subject: [PATCH 5/9] chore: remove webpage alerts

---
 .../insight-sharing/alerting/AlertingForm.tsx | 39 +++++--------------
 .../reporting/ReportingForm.tsx               | 37 +++++-------------
 2 files changed, 19 insertions(+), 57 deletions(-)

diff --git a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
index 0b53289b6..2ec1ad5b5 100644
--- a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
+++ b/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
@@ -7,6 +7,7 @@ import { Button, Input, LoadingSpinner } from '../../components';
 import { EditorState } from 'lexical';
 import { MonitorType } from '../components/Sidebar';
 import { wsUpdateInsight, wsCreateInsight, wsDeleteInsight } from '../../data-access/broker/wsInsightSharing';
+import { addError } from '../../data-access/store/configSlice';
 
 type Props = {
   insight: InsightResponse;
@@ -45,30 +46,22 @@ export function AlertingForm(props: Props) {
 
   const handleSave = async () => {
     if (!name || name.trim() === '') {
-      alert('Please enter a name for the alert.');
-      return;
-    }
-    if (!description || description.trim() === '') {
-      alert('Please enter a description for the alert.');
-      return;
-    }
-    if (!editorState) {
-      alert('Please enter content in the text editor before saving.');
+      dispatch(addError('Name is required'));
       return;
     }
 
-    const alertData : InsightRequest = {
+    const alertData: InsightRequest = {
       name,
       description,
       recipients,
       template: JSON.stringify(editorState),
       saveStateId: session.currentSaveState || '',
       type: 'alert' as const,
-      frequency: ''
+      frequency: '',
     };
 
     setLoading(true);
-    
+
     if (props.insight.id) {
       wsUpdateInsight(props.insight.id, alertData, (data: any, status: string) => {
         setLoading(false);
@@ -124,20 +117,8 @@ export function AlertingForm(props: Props) {
   return (
     <div>
       <span className="text-lg text-secondary-600 font-bold mb-4">Alert ID: {props.insight.id}</span>
-      <Input
-        label="Name"
-        type="text"
-        value={name}
-        onChange={setName}
-        className="mb-4"
-      />
-      <Input
-        label="Description"
-        type="text"
-        value={description}
-        onChange={setDescription}
-        className="mb-4"
-      />
+      <Input label="Name" type="text" value={name} onChange={setName} className="mb-4" />
+      <Input label="Description" type="text" value={description} onChange={setDescription} className="mb-4" />
       <Accordion defaultOpenAll={true} className="border-t divide-y">
         <AccordionItem className="pt-2 pb-4">
           <AccordionHead showArrow={false}>
@@ -152,8 +133,8 @@ export function AlertingForm(props: Props) {
                   setRecipientInput(String(value));
                   const recipientList = String(value)
                     .split(/[, ]/)
-                    .map(r => r.trim())
-                    .filter(r => r.length > 0);
+                    .map((r) => r.trim())
+                    .filter((r) => r.length > 0);
                   setRecipients(recipientList);
                 }}
                 placeholder="Enter recipient(s)"
@@ -183,4 +164,4 @@ export function AlertingForm(props: Props) {
       </div>
     </div>
   );
-}
\ No newline at end of file
+}
diff --git a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
index 0bff6cbbb..fd827f26f 100644
--- a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
+++ b/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
@@ -7,6 +7,7 @@ import { useAppDispatch, useSessionCache } from '../../data-access';
 import { MonitorType } from '../components/Sidebar';
 import { EditorState } from 'lexical';
 import { wsCreateInsight, wsDeleteInsight, wsUpdateInsight } from '../../data-access/broker/wsInsightSharing';
+import { addError } from '../../data-access/store/configSlice';
 
 type Props = {
   insight: InsightResponse;
@@ -59,15 +60,7 @@ export function ReportingForm(props: Props) {
 
   const handleSave = async () => {
     if (!name || name.trim() === '') {
-      alert('Please enter a name for the report.');
-      return;
-    }
-    if (!description || description.trim() === '') {
-      alert('Please enter a description for the report.');
-      return;
-    }
-    if (!editorState) {
-      alert('Please enter content in the text editor before saving.');
+      dispatch(addError('Please enter a name for the report.'));
       return;
     }
 
@@ -77,7 +70,7 @@ export function ReportingForm(props: Props) {
       recipients,
       frequency,
       template: JSON.stringify(editorState),
-      saveStateId: session.currentSaveState || "",
+      saveStateId: session.currentSaveState || '',
       type: 'report' as const,
     };
 
@@ -107,7 +100,7 @@ export function ReportingForm(props: Props) {
           alert('Failed to create report.');
         }
       });
-     }
+    }
   };
 
   const handleDelete = async () => {
@@ -136,20 +129,8 @@ export function ReportingForm(props: Props) {
   ) : (
     <div>
       <span className="text-lg text-secondary-600 font-bold mb-4">Report ID: {props.insight.id}</span>
-      <Input
-        label="Name"
-        type="text"
-        value={name}
-        onChange={handleSetName}
-        className="mb-4"
-      />
-      <Input
-        label="Description"
-        type="text"
-        value={description}
-        onChange={handleSetDescription}
-        className="mb-4"
-      />
+      <Input label="Name" type="text" value={name} onChange={handleSetName} className="mb-4" />
+      <Input label="Description" type="text" value={description} onChange={handleSetDescription} className="mb-4" />
       <Accordion defaultOpenAll={true} className="border-t divide-y">
         <AccordionItem className="pt-2 pb-4">
           <AccordionHead showArrow={false}>
@@ -164,8 +145,8 @@ export function ReportingForm(props: Props) {
                   setRecipientInput(String(value));
                   const recipientList = String(value)
                     .split(/[, ]/)
-                    .map(r => r.trim())
-                    .filter(r => r.length > 0);
+                    .map((r) => r.trim())
+                    .filter((r) => r.length > 0);
                   setRecipients(recipientList);
                 }}
                 placeholder="Enter recipient(s)"
@@ -210,4 +191,4 @@ export function ReportingForm(props: Props) {
       </div>
     </div>
   );
-}
\ No newline at end of file
+}
-- 
GitLab


From 9d73651b2a4fdf15c940cb7b1ba790fda178b6a0 Mon Sep 17 00:00:00 2001
From: Dennis Collaris <d.collaris@me.com>
Date: Wed, 4 Dec 2024 13:06:46 +0100
Subject: [PATCH 6/9] feat: add import/export capabilities to variable nodes

---
 .../components/textEditor/VariableNode.tsx    | 26 ++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/libs/shared/lib/components/textEditor/VariableNode.tsx b/libs/shared/lib/components/textEditor/VariableNode.tsx
index 300242055..a2341e498 100644
--- a/libs/shared/lib/components/textEditor/VariableNode.tsx
+++ b/libs/shared/lib/components/textEditor/VariableNode.tsx
@@ -1,4 +1,4 @@
-import type { NodeKey, LexicalEditor, DOMExportOutput } from 'lexical';
+import type { NodeKey, LexicalEditor, DOMExportOutput, SerializedLexicalNode, Spread } from 'lexical';
 import { DecoratorNode, EditorConfig } from 'lexical';
 
 export enum VariableType {
@@ -6,6 +6,14 @@ export enum VariableType {
   visualization = 'visualization',
 }
 
+export type SerializedVariableNode = Spread<
+  {
+    name: string,
+    variableType: VariableType
+  },
+  SerializedLexicalNode
+>;
+
 export class VariableNode extends DecoratorNode<JSX.Element> {
   __name: string;
   __variableType: VariableType;
@@ -39,6 +47,22 @@ export class VariableNode extends DecoratorNode<JSX.Element> {
     return `{{ ${self.__variableType}:${self.__name} }}`;
   }
 
+  // Import and export
+
+  exportJSON(): SerializedVariableNode {
+    return {
+      type: this.getType(),
+      variableType: this.__variableType,
+      name: this.__name,
+      version: 1,
+    };
+  }
+
+  static importJSON(jsonNode: SerializedVariableNode): VariableNode {
+    const node = new VariableNode(jsonNode.name, jsonNode.variableType);
+    return node;
+  }
+
   // View
 
   createDOM(config: EditorConfig): HTMLElement {
-- 
GitLab


From fac6a8827d9b17c66ecd4a177dcaaa95dc7f9f80 Mon Sep 17 00:00:00 2001
From: Leonardo <leomilho@gmail.com>
Date: Tue, 3 Dec 2024 18:40:01 +0100
Subject: [PATCH 7/9] chore: style fixes and missing code fixes

---
 libs/shared/lib/data-access/api/eventBus.tsx  |  4 +-
 .../data-access/broker/wsInsightSharing.ts    | 40 ++++-----
 libs/shared/lib/data-access/store/hooks.ts    |  4 +
 .../data-access/store/insightSharingSlice.ts  | 36 +++-----
 .../AlertingForm.tsx => FormAlert.tsx}        | 31 ++++---
 .../ReportingForm.tsx => FormReport.tsx}      | 30 ++++---
 .../lib/insight-sharing/InsightDialog.tsx     | 55 +++++++-----
 .../lib/insight-sharing/SettingsPanel.tsx     | 88 ------------------
 .../insight-sharing/components/AddItem.tsx    | 23 ++---
 .../insight-sharing/components/Sidebar.tsx    | 89 +++++++++----------
 .../components/StartScreen.tsx                | 11 +--
 11 files changed, 161 insertions(+), 250 deletions(-)
 rename libs/shared/lib/insight-sharing/{alerting/AlertingForm.tsx => FormAlert.tsx} (84%)
 rename libs/shared/lib/insight-sharing/{reporting/ReportingForm.tsx => FormReport.tsx} (87%)
 delete mode 100644 libs/shared/lib/insight-sharing/SettingsPanel.tsx

diff --git a/libs/shared/lib/data-access/api/eventBus.tsx b/libs/shared/lib/data-access/api/eventBus.tsx
index 03d9e7f29..d58c915cf 100644
--- a/libs/shared/lib/data-access/api/eventBus.tsx
+++ b/libs/shared/lib/data-access/api/eventBus.tsx
@@ -43,14 +43,14 @@ import {
   setFetchingSaveStates,
   setStateAuthorization,
 } from '../store/sessionSlice';
-import { URLParams, getParam, deleteParam } from './url';
+import { URLParams, getParam } from './url';
 import { VisState, setVisualizationState } from '../store/visualizationSlice';
 import { isEqual } from 'lodash-es';
 import { setSchemaAttributeDimensions, setSchemaAttributeInformation, setSchemaLoading } from '../store/schemaSlice';
 import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 import { unSelect } from '../store/interactionSlice';
 import { SchemaGraphStats } from '../../schema';
-import { wsUserGetPolicy, wsUserPolicyCheck } from '../broker/wsUser';
+import { wsUserGetPolicy } from '../broker/wsUser';
 import { authorized } from '../store/authSlice';
 
 export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }) => {
diff --git a/libs/shared/lib/data-access/broker/wsInsightSharing.ts b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
index b9432157e..04c366a3e 100644
--- a/libs/shared/lib/data-access/broker/wsInsightSharing.ts
+++ b/libs/shared/lib/data-access/broker/wsInsightSharing.ts
@@ -11,7 +11,7 @@ export function wsGetInsights(saveStateId: string, callback?: GetInsightsRespons
     {
       key: 'insight',
       subKey: 'getAll',
-      body: JSON.stringify({saveStateId: saveStateId})
+      body: JSON.stringify({ saveStateId: saveStateId })
     },
     internalCallback
   );
@@ -27,29 +27,29 @@ export function wsCreateInsight(insight: InsightRequest, callback?: Function) {
     (data: any, status: string) => {
       if (status === 'Bad Request') {
         console.error('Failed to create insight:', data);
-      if (callback) callback(data, status);
-      return;
-    }
+        if (callback) callback(data, status);
+        return;
+      }
 
-    if (!data || typeof data !== 'object') {
-      console.error('Invalid repsonse data', data)
-      if (callback) callback(null, 'error');
-      return;
-    }
+      if (!data || typeof data !== 'object') {
+        console.error('Invalid repsonse data', data)
+        if (callback) callback(null, 'error');
+        return;
+      }
 
-    if (!data.type || !data.id) {
-      console.error('Missing fields in response', data)
-      if (callback) callback(null, 'error');
-      return;
-    }
+      if (!data.type || !data.id) {
+        console.error('Missing fields in response', data)
+        if (callback) callback(null, 'error');
+        return;
+      }
 
-    if (callback) callback(data,status);
+      if (callback) callback(data, status);
     }
   );
 }
 
-export function wsUpdateInsight( 
-  id: string, 
+export function wsUpdateInsight(
+  id: string,
   insight: InsightRequest,
   callback?: (data: any, status: string) => void
 ) {
@@ -57,7 +57,7 @@ export function wsUpdateInsight(
     {
       key: 'insight',
       subKey: 'update',
-      body: JSON.stringify({id: id, insight: insight}),
+      body: JSON.stringify({ id: id, insight: insight }),
     },
     (data: any, status: string) => {
       if (status === 'Bad Request') {
@@ -68,13 +68,13 @@ export function wsUpdateInsight(
   );
 }
 
-export function wsDeleteInsight(id: string, type: InsightType, callback?: Function) {
+export function wsDeleteInsight(id: string, callback?: Function) {
 
   Broker.instance().sendMessage(
     {
       key: 'insight',
       subKey: 'delete',
-      body: JSON.stringify({ id, type }),
+      body: JSON.stringify({ id }),
     },
     (data: any, status: string) => {
       if (status === 'Bad Request') {
diff --git a/libs/shared/lib/data-access/store/hooks.ts b/libs/shared/lib/data-access/store/hooks.ts
index 7df070ec3..444a28b38 100644
--- a/libs/shared/lib/data-access/store/hooks.ts
+++ b/libs/shared/lib/data-access/store/hooks.ts
@@ -44,6 +44,7 @@ import { PolicyUsersState, selectPolicyState } from './authorizationUsersSlice';
 import { PolicyResourcesState, selectResourcesPolicyState } from './authorizationResourcesSlice';
 import { SaveStateAuthorizationHeaders, SaveStateI } from '..';
 import { GraphStatistics } from '../../statistics';
+import { InsightResponse, selectInsights } from './insightSharingSlice';
 
 // Use throughout your app instead of plain `useDispatch` and `useSelector`
 export const useAppDispatch: () => AppDispatch = useDispatch;
@@ -100,3 +101,6 @@ export const useUsersPolicy: () => PolicyUsersState = () => useAppSelector(selec
 
 // Authorization Resources Slice
 export const useResourcesPolicy: () => PolicyResourcesState = () => useAppSelector(selectResourcesPolicyState);
+
+// Insights - Reports and Alerts
+export const useInsights: () => InsightResponse[] = () => useAppSelector(selectInsights);
\ No newline at end of file
diff --git a/libs/shared/lib/data-access/store/insightSharingSlice.ts b/libs/shared/lib/data-access/store/insightSharingSlice.ts
index d9485069d..a3a4105e0 100644
--- a/libs/shared/lib/data-access/store/insightSharingSlice.ts
+++ b/libs/shared/lib/data-access/store/insightSharingSlice.ts
@@ -21,13 +21,9 @@ export type InsightResponse = {
 
 type InsightState = {
   insights: InsightResponse[];
-  reports: InsightResponse[];
-  alerts: InsightResponse[];
 }
 
 const initialState: InsightState = {
-  reports: [],
-  alerts: [],
   insights: []
 };
 
@@ -35,10 +31,8 @@ const insightSharingSlice = createSlice({
   name: 'insightSharing',
   initialState,
   reducers: {
-    setInsights(state, action: PayloadAction<InsightResponse[] | { reports: InsightResponse[] }>) {
-      const insights = Array.isArray(action.payload) ? action.payload : action.payload.reports;
-      state.reports = insights.filter((insight) => insight.type === 'report');
-      state.alerts = insights.filter((insight) => insight.type === 'alert');
+    setInsights(state, action: PayloadAction<InsightResponse[]>) {
+      state.insights = action.payload;
     },
     addInsight: (state, action: PayloadAction<InsightResponse>) => {
       if (!action.payload) {
@@ -52,33 +46,29 @@ const insightSharingSlice = createSlice({
         return;
       }
 
-      if (type === 'report') {
-        state.reports.push(action.payload);
-      } else if (type === 'alert') {
-        state.alerts.push(action.payload);
-      }
+      state.insights.push(action.payload);
     },
     updateInsight(state, action: PayloadAction<InsightResponse>) {
-      const insights = action.payload.type === 'report' ? state.reports : state.alerts;
-      const index = insights.findIndex((i) => i.id === action.payload.id);
+      const index = state.insights.findIndex((i) => i.id === action.payload.id);
       if (index !== -1) {
-        insights[index] = { ...action.payload };
+        state.insights[index] = { ...action.payload };
       } else {
-        insights.push(action.payload);
+        state.insights.push(action.payload);
       }
     },
-    deleteInsight(state, action: PayloadAction<{ id: string; type: 'report' | 'alert' }>) {
-      const insights = action.payload.type === 'report' ? state.reports : state.alerts;
-      const index = insights.findIndex((i) => i.id === action.payload.id);
+    deleteInsight(state, action: PayloadAction<{ id: string; }>) {
+      const index = state.insights.findIndex((i) => i.id === action.payload.id);
       if (index !== -1) {
-        insights.splice(index, 1);
+        state.insights.splice(index, 1);
       }
     },
   },
 });
 
 export const { setInsights, addInsight, updateInsight, deleteInsight } = insightSharingSlice.actions;
-export const selectReports = (state: RootState): InsightResponse[] => state.insightSharing.reports;
-export const selectAlerts = (state: RootState): InsightResponse[] => state.insightSharing.alerts;
+
+export const selectReports = (state: RootState): InsightResponse[] => state.insightSharing.insights.filter((i) => i.type === 'report');
+export const selectAlerts = (state: RootState): InsightResponse[] => state.insightSharing.insights.filter((i) => i.type === 'alert');
+export const selectInsights = (state: RootState): InsightResponse[] => state.insightSharing.insights;
 
 export default insightSharingSlice.reducer;
diff --git a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx b/libs/shared/lib/insight-sharing/FormAlert.tsx
similarity index 84%
rename from libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
rename to libs/shared/lib/insight-sharing/FormAlert.tsx
index 2ec1ad5b5..e9ebe20f7 100644
--- a/libs/shared/lib/insight-sharing/alerting/AlertingForm.tsx
+++ b/libs/shared/lib/insight-sharing/FormAlert.tsx
@@ -1,22 +1,27 @@
 import React, { useEffect, useState } from 'react';
-import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '../../components/accordion';
-import { TextEditor } from '../../components/textEditor';
-import { InsightResponse, updateInsight, deleteInsight, addInsight, InsightRequest } from '../../data-access/store/insightSharingSlice';
-import { useAppDispatch, useSessionCache } from '../../data-access';
-import { Button, Input, LoadingSpinner } from '../../components';
+import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '../components/accordion';
+import { TextEditor } from '../components/textEditor';
+import {
+  InsightResponse,
+  updateInsight,
+  deleteInsight,
+  addInsight,
+  InsightRequest,
+} from '@graphpolaris/shared/lib/data-access/store/insightSharingSlice';
+import { useAppDispatch, useSessionCache } from '@graphpolaris/shared/lib/data-access';
+import { Button, Input, LoadingSpinner } from '../components';
 import { EditorState } from 'lexical';
-import { MonitorType } from '../components/Sidebar';
-import { wsUpdateInsight, wsCreateInsight, wsDeleteInsight } from '../../data-access/broker/wsInsightSharing';
-import { addError } from '../../data-access/store/configSlice';
+import { MonitorType } from './components/Sidebar';
+import { wsUpdateInsight, wsCreateInsight, wsDeleteInsight } from '@graphpolaris/shared/lib/data-access/broker/wsInsightSharing';
+import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 
 type Props = {
   insight: InsightResponse;
-  setActiveCategory?: (val: MonitorType | undefined) => void;
   setActive?: (val: string) => void;
   handleDelete: () => void;
 };
 
-export function AlertingForm(props: Props) {
+export function FormAlert(props: Props) {
   const dispatch = useAppDispatch();
   const session = useSessionCache();
   const [loading, setLoading] = useState(false);
@@ -80,7 +85,6 @@ export function AlertingForm(props: Props) {
           dispatch(addInsight(data));
           console.log('Alert created successfully');
           if (props.setActive) props.setActive(data.id);
-          if (props.setActiveCategory) props.setActiveCategory('alert');
         } else {
           console.error('Failed to create alert:', data);
           alert('Failed to create alert.');
@@ -96,12 +100,11 @@ export function AlertingForm(props: Props) {
     }
 
     setLoading(true);
-    wsDeleteInsight(props.insight.id, 'alert', (data: any, status: string) => {
+    wsDeleteInsight(props.insight.id, (data: any, status: string) => {
       setLoading(false);
       if (status === 'success') {
-        dispatch(deleteInsight({ id: props.insight.id, type: 'alert' }));
+        dispatch(deleteInsight({ id: props.insight.id }));
         if (props.setActive) props.setActive('');
-        if (props.setActiveCategory) props.setActiveCategory(undefined);
         console.log('Alert deleted successfully');
       } else {
         console.error('Failed to delete alert:', data);
diff --git a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx b/libs/shared/lib/insight-sharing/FormReport.tsx
similarity index 87%
rename from libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
rename to libs/shared/lib/insight-sharing/FormReport.tsx
index fd827f26f..de7744595 100644
--- a/libs/shared/lib/insight-sharing/reporting/ReportingForm.tsx
+++ b/libs/shared/lib/insight-sharing/FormReport.tsx
@@ -1,22 +1,26 @@
 import React, { useState, useEffect } from 'react';
-import { Button, Input, LoadingSpinner } from '../../components';
-import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
-import { TextEditor } from '../../components/textEditor';
-import { InsightResponse, updateInsight, deleteInsight, InsightRequest } from '../../data-access/store/insightSharingSlice';
-import { useAppDispatch, useSessionCache } from '../../data-access';
-import { MonitorType } from '../components/Sidebar';
+import { Button, Input, LoadingSpinner } from '../components';
+import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../components/accordion';
+import { TextEditor } from '../components/textEditor';
+import {
+  InsightResponse,
+  updateInsight,
+  deleteInsight,
+  InsightRequest,
+} from '@graphpolaris/shared/lib/data-access/store/insightSharingSlice';
+import { useAppDispatch, useSessionCache } from '@graphpolaris/shared/lib/data-access';
+import { MonitorType } from './components/Sidebar';
 import { EditorState } from 'lexical';
-import { wsCreateInsight, wsDeleteInsight, wsUpdateInsight } from '../../data-access/broker/wsInsightSharing';
-import { addError } from '../../data-access/store/configSlice';
+import { wsCreateInsight, wsDeleteInsight, wsUpdateInsight } from '@graphpolaris/shared/lib/data-access/broker/wsInsightSharing';
+import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 
 type Props = {
   insight: InsightResponse;
-  setActiveCategory: (val: MonitorType | undefined) => void;
   setActive: (val: string) => void;
   handleDelete: () => void;
 };
 
-export function ReportingForm(props: Props) {
+export function FormReport(props: Props) {
   const dispatch = useAppDispatch();
   const session = useSessionCache();
   const [loading, setLoading] = useState(false);
@@ -94,7 +98,6 @@ export function ReportingForm(props: Props) {
           dispatch(updateInsight(data));
           console.log('Report created successfully');
           if (props.setActive) props.setActive(data.id);
-          if (props.setActiveCategory) props.setActiveCategory('report');
         } else {
           console.error('Failed to create report:', data);
           alert('Failed to create report.');
@@ -110,12 +113,11 @@ export function ReportingForm(props: Props) {
     }
 
     setLoading(true);
-    wsDeleteInsight(props.insight.id, props.insight.type, (data: any, status: string) => {
+    wsDeleteInsight(props.insight.id, (data: any, status: string) => {
       setLoading(false);
       if (status === 'success') {
-        dispatch(deleteInsight({ id: props.insight.id, type: props.insight.type }));
+        dispatch(deleteInsight({ id: props.insight.id }));
         props.setActive('');
-        props.setActiveCategory(undefined);
         console.log('Report deleted successfully');
       } else {
         console.error('Failed to delete report:', data);
diff --git a/libs/shared/lib/insight-sharing/InsightDialog.tsx b/libs/shared/lib/insight-sharing/InsightDialog.tsx
index 71d93bf91..44f1d2fe1 100644
--- a/libs/shared/lib/insight-sharing/InsightDialog.tsx
+++ b/libs/shared/lib/insight-sharing/InsightDialog.tsx
@@ -1,7 +1,13 @@
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
 import { Dialog, DialogContent } from '../components';
-import { SettingsPanel } from './SettingsPanel';
 import { MonitorType, Sidebar } from './components/Sidebar';
+import { AddItem } from './components/AddItem';
+import { useAppDispatch, useInsights } from '..';
+import { StartScreen } from './components/StartScreen';
+import { FormReport } from './FormReport';
+import { FormAlert } from './FormAlert';
+import { wsDeleteInsight } from '../data-access/broker/wsInsightSharing';
+import { deleteInsight } from '../data-access/store/insightSharingSlice';
 
 type Props = {
   open: boolean;
@@ -9,13 +15,20 @@ type Props = {
 };
 
 export function InsightDialog(props: Props) {
-  const [adding, setAdding] = useState<boolean>(false);
+  const dispatch = useAppDispatch();
+
+  const [adding, setAdding] = useState<false | MonitorType>(false);
   const [active, setActive] = useState<string>('');
-  const [activeCategory, setActiveCategory] = useState<MonitorType | undefined>(undefined);
+  const insights = useInsights();
+
+  const selectedInsight = useMemo(() => insights.find((i) => i.id === active), [active, insights]);
 
-  const handleChangeActive = (category: MonitorType, id: string) => {
-    setActive(id);
-    setActiveCategory(category);
+  const handleDelete = () => {
+    if (!selectedInsight) return;
+
+    dispatch(deleteInsight({ id: selectedInsight.id }));
+    wsDeleteInsight(selectedInsight.id);
+    setActive('');
   };
 
   return (
@@ -27,19 +40,21 @@ export function InsightDialog(props: Props) {
     >
       <DialogContent className="w-5/6 h-5/6 rounded-none py-0 px-0">
         <div className="flex w-full h-full">
-          <Sidebar
-            changeActive={handleChangeActive}
-            setAdding={setAdding}
-            setActiveCategory={setActiveCategory}
-          />
-          <SettingsPanel
-            activeCategory={activeCategory}
-            active={active}
-            adding={adding}
-            setAdding={setAdding}
-            setActiveCategory={setActiveCategory}
-            setActive={setActive}
-          />
+          <div className="w-1/4 border-r overflow-auto flex flex-col h-full">
+            <Sidebar setAdding={setAdding} setActive={setActive} />
+          </div>
+
+          <div className="w-3/4 p-8">
+            {adding ? (
+              <AddItem setAdding={setAdding} setActive={setActive} type={adding} />
+            ) : !selectedInsight ? (
+              <StartScreen setAdding={setAdding} />
+            ) : selectedInsight.type === 'report' ? (
+              <FormReport insight={selectedInsight} setActive={setActive} handleDelete={handleDelete} />
+            ) : (
+              <FormAlert insight={selectedInsight} setActive={setActive} handleDelete={handleDelete} />
+            )}
+          </div>
         </div>
       </DialogContent>
     </Dialog>
diff --git a/libs/shared/lib/insight-sharing/SettingsPanel.tsx b/libs/shared/lib/insight-sharing/SettingsPanel.tsx
deleted file mode 100644
index ba6d02f74..000000000
--- a/libs/shared/lib/insight-sharing/SettingsPanel.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { ReportingForm } from './reporting/ReportingForm';
-import { AlertingForm } from './alerting/AlertingForm';
-import { AddItem } from './components/AddItem';
-import { StartScreen } from './components/StartScreen';
-import { MonitorType } from './components/Sidebar';
-import { useAppDispatch, useAppSelector } from '../data-access';
-import {
-  deleteInsight,
-  selectReports,
-  selectAlerts,
-  InsightResponse,
-} from '../data-access/store/insightSharingSlice';
-import { wsDeleteInsight } from '../data-access/broker/wsInsightSharing';
-
-type Props = {
-  active: string;
-  activeCategory: MonitorType | undefined;
-  adding: boolean;
-  setAdding: (val: boolean) => void;
-  setActiveCategory: (val: MonitorType | undefined) => void;
-  setActive: (val: string) => void;
-};
-
-export function SettingsPanel(props: Props) {
-  const dispatch = useAppDispatch();
-  const reports = useAppSelector(selectReports);
-  const alerts = useAppSelector(selectAlerts);
-
-  const [currentInsight, setCurrentInsight] = useState<InsightResponse | null>(null);
-
-  useEffect(() => {
-    if (props.activeCategory && props.active) {
-      const insights = props.activeCategory === 'report' ? reports : alerts;
-      const insight = insights.find((i) => i.id === props.active);
-      if (insight) {
-        setCurrentInsight(insight);
-      }
-    } else {
-      setCurrentInsight(null);
-    }
-  }, [props.activeCategory, props.active, reports, alerts]);
-
-  const handleDelete = () => {
-    if (currentInsight) {
-      dispatch(deleteInsight({ id: currentInsight.id, type: currentInsight.type }));
-      wsDeleteInsight(currentInsight.id, currentInsight.type);
-      props.setActive('');
-      props.setActiveCategory(undefined);
-    }
-  };
-
-  return props.activeCategory ? (
-    <div className="w-3/4 p-4">
-      {props.adding ? (
-        <AddItem
-          category={props.activeCategory}
-          setAdding={props.setAdding}
-          setActive={props.setActive}
-          setActiveCategory={props.setActiveCategory}
-        />
-      ) : currentInsight ? (
-        <div>
-          {props.activeCategory === 'report' ? (
-            <ReportingForm
-              insight={currentInsight}
-              setActiveCategory={props.setActiveCategory}
-              setActive={props.setActive}
-              handleDelete={handleDelete}
-            />
-          ) : (
-            <AlertingForm
-              insight={currentInsight}
-              setActiveCategory={props.setActiveCategory}
-              setActive={props.setActive}
-              handleDelete={handleDelete}
-            />
-          )}
-        </div>
-      ) : null}
-    </div>
-  ) : (
-    <StartScreen
-      setAdding={props.setAdding}
-      setActiveCategory={props.setActiveCategory}
-    />
-  );
-}
diff --git a/libs/shared/lib/insight-sharing/components/AddItem.tsx b/libs/shared/lib/insight-sharing/components/AddItem.tsx
index d58e3709b..fe8f3f173 100644
--- a/libs/shared/lib/insight-sharing/components/AddItem.tsx
+++ b/libs/shared/lib/insight-sharing/components/AddItem.tsx
@@ -1,22 +1,19 @@
 import React, { useState } from 'react';
 import { Input, Button } from '../../components';
-import { MonitorType } from './Sidebar';
+import { MonitorType as InsightType, MonitorType } from './Sidebar';
 import { useAppDispatch, useSessionCache } from '../../data-access';
 import { addInsight } from '../../data-access/store/insightSharingSlice';
-import { v4 as uuidv4 } from 'uuid';
 import { wsCreateInsight } from '../../data-access/broker/wsInsightSharing';
 
 type Props = {
-  category: MonitorType;
-  setAdding: (val: boolean) => void;
+  setAdding: (val: false | MonitorType) => void;
   setActive: (val: string) => void;
-  setActiveCategory: (val: MonitorType | undefined) => void;
+  type: InsightType;
 };
 
 export function AddItem(props: Props) {
   const [name, setName] = useState<string>('');
   const [description, setDescription] = useState<string>('');
-  const [loading, setLoading] = useState(false);
   const dispatch = useAppDispatch();
   const session = useSessionCache();
 
@@ -31,30 +28,26 @@ export function AddItem(props: Props) {
       description,
       recipients: [],
       template: '',
-      frequency: props.category === 'report' ? 'Daily' : '',
+      frequency: props.type === 'report' ? 'Daily' : '',
       saveStateId: session.currentSaveState || '',
-      type: props.category,
+      type: props.type,
     };
-    setLoading(true);
-    
-    
+
     wsCreateInsight(newInsight, (data: any, status: string) => {
-      setLoading(false);
       if (status === 'success') {
         dispatch(addInsight(data));
         props.setActive(data.id);
-        props.setActiveCategory(props.category);
         props.setAdding(false);
       } else {
         console.error('Failed to create insight:', data);
-        alert('Failed to create new ' + props.category);
+        alert('Failed to create new ' + props.type);
       }
     });
   };
 
   return (
     <div>
-      <span className="text-lg text-secondary-600 font-bold mb-4">Add a new {props.category}ing service</span>
+      <span className="text-lg text-secondary-600 font-bold mb-4">Add a new {props.type}ing service</span>
       <Input type="text" label="Name" value={name} onChange={setName} className="mb-2" />
       <Input type="text" label="Description" value={description} onChange={setDescription} className="mb-2" />
       <Button label="Save" onClick={handleSave} disabled={!name || !description} className="mt-2" />
diff --git a/libs/shared/lib/insight-sharing/components/Sidebar.tsx b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
index a3b7bd841..96aefb1f1 100644
--- a/libs/shared/lib/insight-sharing/components/Sidebar.tsx
+++ b/libs/shared/lib/insight-sharing/components/Sidebar.tsx
@@ -1,7 +1,7 @@
 import React, { useEffect } from 'react';
 import { Button } from '../../components';
 import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
-import { useAppDispatch, useAppSelector } from '../../data-access';
+import { useAppDispatch, useInsights } from '../../data-access';
 import { setInsights } from '../../data-access/store/insightSharingSlice';
 import { useSessionCache } from '../../data-access';
 import { wsGetInsights } from '../../data-access/broker/wsInsightSharing';
@@ -9,42 +9,34 @@ import { wsGetInsights } from '../../data-access/broker/wsInsightSharing';
 export type MonitorType = 'report' | 'alert';
 
 type SidebarProps = {
-  changeActive: (category: MonitorType, val: string) => void;
-  setAdding: (val: boolean) => void;
-  setActiveCategory: (val: MonitorType | undefined) => void;
+  setAdding: (val: false | MonitorType) => void;
+  setActive: (val: string) => void;
 };
 
 export function Sidebar(props: SidebarProps) {
   const dispatch = useAppDispatch();
   const session = useSessionCache();
-  const reports = useAppSelector((state) => state.insightSharing.reports);
-  const alerts = useAppSelector((state) => state.insightSharing.alerts);
+  const insights = useInsights();
 
   useEffect(() => {
-    async function fetchReports() {
-      if (session.currentSaveState && session.currentSaveState !== '') {
-        wsGetInsights(session.currentSaveState, (data: any, status: string)=>{
-          dispatch(setInsights(data));
-        })
-      }
+    if (session.currentSaveState && session.currentSaveState !== '') {
+      wsGetInsights(session.currentSaveState, (data: any, status: string) => {
+        dispatch(setInsights(data));
+      });
     }
-    
-    fetchReports();
-  }, [session.currentSaveState, dispatch]);
-
+  }, [session.currentSaveState]);
 
   return (
-    <div className="w-1/4 border-r overflow-auto flex flex-col h-full">
+    <>
       <span className="text-lg text-secondary-700 font-semibold px-2 py-4">Insight Sharing</span>
-      <Accordion defaultOpenIndex={0}>
+      <Accordion defaultOpenAll={true}>
         <AccordionItem className="">
           <AccordionHead className="border-b bg-secondary-50 hover:bg-secondary-100 p-1">
             <div className="w-full flex justify-between">
               <span className="font-semibold">Reports</span>
               <div
                 onClick={(e) => {
-                  props.setAdding(true);
-                  props.setActiveCategory('report');
+                  props.setAdding('report');
                   e.stopPropagation();
                 }}
               >
@@ -61,18 +53,20 @@ export function Sidebar(props: SidebarProps) {
           </AccordionHead>
           <AccordionBody className="ml-0">
             <ul className="space-y-2">
-              {reports.map((report) => (
-                <li
-                  key={report.id}
-                  className="cursor-pointer p-2 hover:bg-secondary-50"
-                  onClick={() => {
-                    props.changeActive('report', report.id);
-                    props.setAdding(false);
-                  }}
-                >
-                  {report.name}
-                </li>
-              ))}
+              {insights
+                .filter((insight) => insight.type === 'report')
+                .map((report) => (
+                  <li
+                    key={report.id}
+                    className="cursor-pointer p-2 hover:bg-secondary-50"
+                    onClick={() => {
+                      props.setAdding(false);
+                      props.setActive(report.id);
+                    }}
+                  >
+                    {report.name}
+                  </li>
+                ))}
             </ul>
           </AccordionBody>
         </AccordionItem>
@@ -82,8 +76,7 @@ export function Sidebar(props: SidebarProps) {
               <span className="font-semibold">Alerts</span>
               <div
                 onClick={(e) => {
-                  props.setAdding(true);
-                  props.setActiveCategory('alert');
+                  props.setAdding('alert');
                   e.stopPropagation();
                 }}
               >
@@ -100,22 +93,24 @@ export function Sidebar(props: SidebarProps) {
           </AccordionHead>
           <AccordionBody className="ml-0">
             <ul className="space-y-2">
-              {alerts.map((alert) => (
-                <li
-                  key={alert.id}
-                  className="cursor-pointer p-2 hover:bg-secondary-50"
-                  onClick={() => {
-                    props.changeActive('alert', alert.id);
-                    props.setAdding(false);
-                  }}
-                >
-                  {alert.name}
-                </li>
-              ))}
+              {insights
+                .filter((insight) => insight.type === 'alert')
+                .map((alert) => (
+                  <li
+                    key={alert.id}
+                    className="cursor-pointer p-2 hover:bg-secondary-50"
+                    onClick={() => {
+                      props.setAdding(false);
+                      props.setActive(alert.id);
+                    }}
+                  >
+                    {alert.name}
+                  </li>
+                ))}
             </ul>
           </AccordionBody>
         </AccordionItem>
       </Accordion>
-    </div>
+    </>
   );
 }
diff --git a/libs/shared/lib/insight-sharing/components/StartScreen.tsx b/libs/shared/lib/insight-sharing/components/StartScreen.tsx
index 801331c40..377ad49d9 100644
--- a/libs/shared/lib/insight-sharing/components/StartScreen.tsx
+++ b/libs/shared/lib/insight-sharing/components/StartScreen.tsx
@@ -3,13 +3,12 @@ import { Button, Icon } from '../../components';
 import { MonitorType } from './Sidebar';
 
 type Props = {
-  setAdding: (val: boolean) => void;
-  setActiveCategory: (val: MonitorType | undefined) => void;
+  setAdding: (val: false | MonitorType) => void;
 };
 
 export function StartScreen(props: Props) {
   return (
-    <div className="w-full flex justify-center items-center">
+    <div className="w-full h-full flex justify-center items-center">
       <div className="">
         <span className="text-lg text-secondary-700 font-bold mb-4">Start</span>
         <div>
@@ -19,8 +18,7 @@ export function StartScreen(props: Props) {
             variant="outline"
             className="mb-2"
             onClick={() => {
-              props.setAdding(true);
-              props.setActiveCategory('report');
+              props.setAdding('report');
             }}
           />
           <Button
@@ -28,8 +26,7 @@ export function StartScreen(props: Props) {
             label="New alert"
             variant="outline"
             onClick={() => {
-              props.setAdding(true);
-              props.setActiveCategory('alert');
+              props.setAdding('alert');
             }}
           />
         </div>
-- 
GitLab


From 9881c8b5f6c68db6b2ad0b5d155589dec99dfc1b Mon Sep 17 00:00:00 2001
From: Samed <sbalcioglu@graphpolaris.com>
Date: Fri, 6 Dec 2024 16:46:14 +0100
Subject: [PATCH 8/9] fix: insight saves properly in fe & be

---
 .../lib/components/textEditor/TextEditor.tsx  | 59 +++++++++++------
 .../plugins/InsertVariablesPlugin.tsx         |  1 +
 .../textEditor/plugins/SaveButtonPlugin.tsx   | 18 ++++++
 libs/shared/lib/insight-sharing/FormAlert.tsx | 63 +++++++++---------
 .../shared/lib/insight-sharing/FormReport.tsx | 64 ++++++++++---------
 5 files changed, 128 insertions(+), 77 deletions(-)
 create mode 100644 libs/shared/lib/components/textEditor/plugins/SaveButtonPlugin.tsx

diff --git a/libs/shared/lib/components/textEditor/TextEditor.tsx b/libs/shared/lib/components/textEditor/TextEditor.tsx
index 5c8714452..32664ad31 100644
--- a/libs/shared/lib/components/textEditor/TextEditor.tsx
+++ b/libs/shared/lib/components/textEditor/TextEditor.tsx
@@ -1,43 +1,61 @@
-import { useRef } from 'react';
+import { useEffect, useRef, useCallback } from 'react';
 import { LexicalComposer } from '@lexical/react/LexicalComposer';
 import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
 import { ContentEditable } from '@lexical/react/LexicalContentEditable';
 import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
-import { LexicalEditor, EditorState } from 'lexical';
-import { $generateHtmlFromNodes } from '@lexical/html';
-import { MyOnChangePlugin } from './plugins/StateChangePlugin';
+import { SerializedEditorState} from 'lexical';
 import { ToolbarPlugin } from './plugins/ToolbarPlugin';
 import { PreviewPlugin } from './plugins/PreviewPlugin';
 import { InsertVariablesPlugin } from './plugins/InsertVariablesPlugin';
 import { ErrorHandler } from './ErrorHandler';
 import { Placeholder } from './Placeholder';
 import { VariableNode } from './VariableNode';
-import { fontFamily } from 'html2canvas/dist/types/css/property-descriptors/font-family';
+import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
+import { SaveButtonPlugin } from './plugins/SaveButtonPlugin';
 
 type TextEditorProps = {
-  editorState: EditorState | null;
-  setEditorState: (value: EditorState) => void;
+  children: React.ReactNode;
+  editorState: SerializedEditorState | null;
+  setEditorState: (value: SerializedEditorState) => void;
   showToolbar: boolean;
   placeholder?: string;
+  handleSave: (elements: SerializedEditorState) => void;
 };
 
-export function TextEditor({ editorState, setEditorState, showToolbar, placeholder }: TextEditorProps) {
-  function onChange(editorState: EditorState, editor: LexicalEditor) {
-    setEditorState(editorState);
-    editor.read(() => {
-      // TODO:
+function InitialStateLoader({ editorState }: { editorState: SerializedEditorState | null}) {
+  const [editor] = useLexicalComposerContext();
+  const previousEditorStateRef = useRef<string | null>(null);
+  
+  useEffect(() => {
+    if (!editor || !editorState) return;
+    queueMicrotask(() => { 
+      editor.setEditorState(editor.parseEditorState(editorState));
     });
-  }
+    
+  }, [editor, editorState]);
+
+  return null;
+}
+
+export function TextEditor({ editorState, showToolbar, placeholder, handleSave, children }: TextEditorProps) {
+  const contentEditableRef = useRef<HTMLDivElement>(null);
+  const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const isUpdatingRef = useRef(false);
+
+  useEffect(() => {
+    return () => {
+      if (updateTimeoutRef.current) {
+        clearTimeout(updateTimeoutRef.current);
+      }
+    };
+  }, []);
 
   const initialConfig = {
     namespace: 'MyEditor',
-    editorState: editorState  ? JSON.stringify(editorState) : null,
     onError: ErrorHandler,
     nodes: [VariableNode],
   };
 
-  const contentEditableRef = useRef<HTMLDivElement>(null);
-
   return (
     <LexicalComposer initialConfig={initialConfig}>
       <div className="editor-toolbar flex items-center bg-secondary-50 rounded mt-4 space-x-2">
@@ -54,8 +72,13 @@ export function TextEditor({ editorState, setEditorState, showToolbar, placehold
         </div>
         <div className="preview min-h-24 p-3 hidden"></div>
       </div>
-      <MyOnChangePlugin onChange={onChange} />
       <InsertVariablesPlugin />
+      <InitialStateLoader editorState={editorState} />
+      <div className='flex justify-end mt-3'>
+        {children}
+        <SaveButtonPlugin 
+        onChange={handleSave}></SaveButtonPlugin>
+      </div>
     </LexicalComposer>
   );
-}
+}
\ No newline at end of file
diff --git a/libs/shared/lib/components/textEditor/plugins/InsertVariablesPlugin.tsx b/libs/shared/lib/components/textEditor/plugins/InsertVariablesPlugin.tsx
index c854fe4a2..c996a62e5 100644
--- a/libs/shared/lib/components/textEditor/plugins/InsertVariablesPlugin.tsx
+++ b/libs/shared/lib/components/textEditor/plugins/InsertVariablesPlugin.tsx
@@ -44,6 +44,7 @@ export const InsertVariablesPlugin = () => {
     <>
       {nodeTypes.map((nodeType) => (
         <Input
+          key={nodeType}
           type="dropdown"
           label={`${nodeType} variable`}
           value=""
diff --git a/libs/shared/lib/components/textEditor/plugins/SaveButtonPlugin.tsx b/libs/shared/lib/components/textEditor/plugins/SaveButtonPlugin.tsx
new file mode 100644
index 000000000..b4c8944ed
--- /dev/null
+++ b/libs/shared/lib/components/textEditor/plugins/SaveButtonPlugin.tsx
@@ -0,0 +1,18 @@
+import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
+import { Button } from '../../buttons';
+import { SerializedEditorState } from 'lexical';
+
+export function SaveButtonPlugin({ onChange }: { onChange: (elements: SerializedEditorState) => void }) {
+    const [editor] = useLexicalComposerContext();
+
+    function handleSave() {
+        editor.read(() => {
+            const data = editor.getEditorState();
+            const jsonData = data.toJSON();
+
+           onChange(jsonData);
+        })
+    }
+
+    return <Button label="Save" variantType="primary" className="ml-2" onClick={handleSave} />;
+}
diff --git a/libs/shared/lib/insight-sharing/FormAlert.tsx b/libs/shared/lib/insight-sharing/FormAlert.tsx
index e9ebe20f7..e010d4474 100644
--- a/libs/shared/lib/insight-sharing/FormAlert.tsx
+++ b/libs/shared/lib/insight-sharing/FormAlert.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
 import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '../components/accordion';
 import { TextEditor } from '../components/textEditor';
 import {
@@ -10,10 +10,9 @@ import {
 } from '@graphpolaris/shared/lib/data-access/store/insightSharingSlice';
 import { useAppDispatch, useSessionCache } from '@graphpolaris/shared/lib/data-access';
 import { Button, Input, LoadingSpinner } from '../components';
-import { EditorState } from 'lexical';
-import { MonitorType } from './components/Sidebar';
 import { wsUpdateInsight, wsCreateInsight, wsDeleteInsight } from '@graphpolaris/shared/lib/data-access/broker/wsInsightSharing';
 import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
+import { SerializedEditorState } from 'lexical';
 
 type Props = {
   insight: InsightResponse;
@@ -29,27 +28,36 @@ export function FormAlert(props: Props) {
   const [description, setDescription] = useState(props.insight.description);
   const [recipients, setRecipients] = useState<string[]>(props.insight.recipients || []);
   const [recipientInput, setRecipientInput] = useState<string>('');
-  const [editorState, setEditorState] = useState<EditorState | null>(null);
+  const [editorState, setEditorState] = useState<SerializedEditorState | null>(null);
 
   useEffect(() => {
-    setName(props.insight.name);
-    setDescription(props.insight.description);
-    setRecipientInput(props.insight.recipients ? props.insight.recipients.join(', ') : '');
-    setRecipients(props.insight.recipients || []);
-    if (props.insight.template) {
-      try {
-        const parsedTemplate = JSON.parse(props.insight.template);
-        setEditorState(parsedTemplate);
-      } catch (e) {
+    let isMounted = true;
+
+    if (isMounted) {
+      setName(props.insight.name);
+      setDescription(props.insight.description);
+      setRecipientInput(props.insight.recipients ? props.insight.recipients.join(', ') : '');
+      setRecipients(props.insight.recipients || []);
+      if (props.insight.template) {
+        try {
+          const parsedTemplate = JSON.parse(props.insight.template);
+          setEditorState(parsedTemplate);
+        } catch (e) {
+          setEditorState(null);
+          console.error('Failed to parse template:', e);
+        }
+      } else {
         setEditorState(null);
-        console.error('Failed to parse template:', e);
       }
-    } else {
-      setEditorState(null);
     }
+
+    return () => {
+      isMounted = false;
+      setEditorState(null);
+    };
   }, [props.insight]);
 
-  const handleSave = async () => {
+  const handleSave = async (element: SerializedEditorState) => {
     if (!name || name.trim() === '') {
       dispatch(addError('Name is required'));
       return;
@@ -59,7 +67,7 @@ export function FormAlert(props: Props) {
       name,
       description,
       recipients,
-      template: JSON.stringify(editorState),
+      template: JSON.stringify(element),
       saveStateId: session.currentSaveState || '',
       type: 'alert' as const,
       frequency: '',
@@ -151,20 +159,17 @@ export function FormAlert(props: Props) {
             <span className="font-semibold">Alerting text</span>
           </AccordionHead>
           <AccordionBody>
-            <TextEditor
-              key={props.insight.id || 'new'}
-              editorState={editorState}
-              setEditorState={setEditorState}
-              showToolbar={true}
-              placeholder="Start typing your alert template..."
-            />
+          <TextEditor
+                key={`editor-${props.insight.id}`}
+                editorState={editorState}
+                setEditorState={setEditorState}
+                showToolbar={true}
+                placeholder="Start typing your alert template..."
+                handleSave={handleSave}
+              ><Button label="Delete" variantType="secondary" variant="outline" onClick={handleDelete} /></TextEditor>
           </AccordionBody>
         </AccordionItem>
       </Accordion>
-      <div className="flex justify-end mt-2">
-        <Button label="Delete" variantType="secondary" variant="outline" onClick={handleDelete} />
-        <Button label="Save" variantType="primary" className="ml-2" onClick={handleSave} />
-      </div>
     </div>
   );
 }
diff --git a/libs/shared/lib/insight-sharing/FormReport.tsx b/libs/shared/lib/insight-sharing/FormReport.tsx
index de7744595..759737574 100644
--- a/libs/shared/lib/insight-sharing/FormReport.tsx
+++ b/libs/shared/lib/insight-sharing/FormReport.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import { useState, useEffect } from 'react';
 import { Button, Input, LoadingSpinner } from '../components';
 import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../components/accordion';
 import { TextEditor } from '../components/textEditor';
@@ -9,8 +9,7 @@ import {
   InsightRequest,
 } from '@graphpolaris/shared/lib/data-access/store/insightSharingSlice';
 import { useAppDispatch, useSessionCache } from '@graphpolaris/shared/lib/data-access';
-import { MonitorType } from './components/Sidebar';
-import { EditorState } from 'lexical';
+import { SerializedEditorState } from 'lexical';
 import { wsCreateInsight, wsDeleteInsight, wsUpdateInsight } from '@graphpolaris/shared/lib/data-access/broker/wsInsightSharing';
 import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 
@@ -29,25 +28,33 @@ export function FormReport(props: Props) {
   const [recipients, setRecipients] = useState(props.insight.recipients || []);
   const [recipientInput, setRecipientInput] = useState<string>('');
   const [frequency, setFrequency] = useState(props.insight.frequency || 'Daily');
-  const [editorState, setEditorState] = useState<EditorState | null>(null);
+  const [editorState, setEditorState] = useState<SerializedEditorState| null>(null);
 
   useEffect(() => {
-    setName(props.insight.name);
-    setDescription(props.insight.description);
-    setRecipientInput(props.insight.recipients ? props.insight.recipients.join(', ') : '');
-    setRecipients(props.insight.recipients || []);
-    setFrequency(props.insight.frequency || 'Daily');
-    if (props.insight.template) {
-      try {
-        const parsedTemplate = JSON.parse(props.insight.template);
-        setEditorState(parsedTemplate);
-      } catch (e) {
+    let isMounted = true;
+    
+    if (isMounted) {
+      setName(props.insight.name);
+      setDescription(props.insight.description);
+      setRecipientInput(props.insight.recipients ? props.insight.recipients.join(', ') : '');
+      setRecipients(props.insight.recipients || []);
+      if (props.insight.template) {
+        try {
+          const parsedTemplate = JSON.parse(props.insight.template);
+          setEditorState(parsedTemplate);
+        } catch (e) {
+          setEditorState(null);
+          console.error('Failed to parse template:', e);
+        }
+      } else {
         setEditorState(null);
-        console.error('Failed to parse template:', e);
       }
-    } else {
-      setEditorState(null);
     }
+  
+    return () => {
+      isMounted = false;
+      setEditorState(null);
+    };
   }, [props.insight]);
 
   const handleSetFrequency = (value: string | number) => {
@@ -62,7 +69,7 @@ export function FormReport(props: Props) {
     setDescription(value as string);
   };
 
-  const handleSave = async () => {
+  const handleSave = async (elements: SerializedEditorState) => {
     if (!name || name.trim() === '') {
       dispatch(addError('Please enter a name for the report.'));
       return;
@@ -73,7 +80,7 @@ export function FormReport(props: Props) {
       description,
       recipients,
       frequency,
-      template: JSON.stringify(editorState),
+      template: JSON.stringify(elements),
       saveStateId: session.currentSaveState || '',
       type: 'report' as const,
     };
@@ -177,20 +184,17 @@ export function FormReport(props: Props) {
             <span className="font-semibold">Email Template</span>
           </AccordionHead>
           <AccordionBody>
-            <TextEditor
-              key={props.insight.id || 'new'}
-              editorState={editorState}
-              setEditorState={setEditorState}
-              showToolbar={true}
-              placeholder="Start typing your report template..."
-            />
+              <TextEditor
+                key={`editor-${props.insight.id}`}
+                editorState={editorState}
+                setEditorState={setEditorState}
+                showToolbar={true}
+                placeholder="Start typing your alert template..."
+                handleSave={handleSave}
+              ><Button label="Delete" variantType="secondary" variant="outline" onClick={handleDelete} /></TextEditor>
           </AccordionBody>
         </AccordionItem>
       </Accordion>
-      <div className="flex justify-end mt-2">
-        <Button label="Delete" variantType="secondary" variant="outline" onClick={handleDelete} />
-        <Button label="Save" variantType="primary" className="ml-2" onClick={handleSave} />
-      </div>
     </div>
   );
 }
-- 
GitLab


From d6878f4f142ce4c9f8ef51fd4debd814beaf6ab6 Mon Sep 17 00:00:00 2001
From: Samed <sbalcioglu@graphpolaris.com>
Date: Mon, 9 Dec 2024 13:51:15 +0100
Subject: [PATCH 9/9] chore: added correct alerts & confirmation msg

---
 libs/shared/lib/insight-sharing/FormAlert.tsx       | 13 ++++++++-----
 libs/shared/lib/insight-sharing/FormReport.tsx      | 13 ++++++++-----
 .../lib/insight-sharing/components/AddItem.tsx      |  6 ++++--
 3 files changed, 20 insertions(+), 12 deletions(-)

diff --git a/libs/shared/lib/insight-sharing/FormAlert.tsx b/libs/shared/lib/insight-sharing/FormAlert.tsx
index e010d4474..4f4d2c6af 100644
--- a/libs/shared/lib/insight-sharing/FormAlert.tsx
+++ b/libs/shared/lib/insight-sharing/FormAlert.tsx
@@ -11,7 +11,7 @@ import {
 import { useAppDispatch, useSessionCache } from '@graphpolaris/shared/lib/data-access';
 import { Button, Input, LoadingSpinner } from '../components';
 import { wsUpdateInsight, wsCreateInsight, wsDeleteInsight } from '@graphpolaris/shared/lib/data-access/broker/wsInsightSharing';
-import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
+import { addError, addSuccess } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 import { SerializedEditorState } from 'lexical';
 
 type Props = {
@@ -80,10 +80,11 @@ export function FormAlert(props: Props) {
         setLoading(false);
         if (status === 'success') {
           dispatch(updateInsight(data));
+          dispatch(addSuccess('Alert updated successfully'));
           console.log('Alert updated successfully');
         } else {
           console.error('Failed to update alert:', data);
-          alert('Failed to update alert.');
+          dispatch(addError('Failed to update alert.'));
         }
       });
     } else {
@@ -91,11 +92,12 @@ export function FormAlert(props: Props) {
         setLoading(false);
         if (status === 'success') {
           dispatch(addInsight(data));
+          dispatch(addSuccess('Alert created successfully'));
           console.log('Alert created successfully');
           if (props.setActive) props.setActive(data.id);
         } else {
           console.error('Failed to create alert:', data);
-          alert('Failed to create alert.');
+          dispatch(addError('Failed to create alert.'));
         }
       });
     }
@@ -103,7 +105,7 @@ export function FormAlert(props: Props) {
 
   const handleDelete = async () => {
     if (!props.insight.id) {
-      alert('Cannot delete an alert without an ID.');
+      dispatch(addError('Cannot delete an alert without an ID.'));
       return;
     }
 
@@ -113,10 +115,11 @@ export function FormAlert(props: Props) {
       if (status === 'success') {
         dispatch(deleteInsight({ id: props.insight.id }));
         if (props.setActive) props.setActive('');
+        dispatch(addSuccess('Alert deleted successfully'));
         console.log('Alert deleted successfully');
       } else {
         console.error('Failed to delete alert:', data);
-        alert('Failed to delete alert.');
+        dispatch(addError('Failed to delete alert'));
       }
     });
   };
diff --git a/libs/shared/lib/insight-sharing/FormReport.tsx b/libs/shared/lib/insight-sharing/FormReport.tsx
index 759737574..e41546b2f 100644
--- a/libs/shared/lib/insight-sharing/FormReport.tsx
+++ b/libs/shared/lib/insight-sharing/FormReport.tsx
@@ -11,7 +11,7 @@ import {
 import { useAppDispatch, useSessionCache } from '@graphpolaris/shared/lib/data-access';
 import { SerializedEditorState } from 'lexical';
 import { wsCreateInsight, wsDeleteInsight, wsUpdateInsight } from '@graphpolaris/shared/lib/data-access/broker/wsInsightSharing';
-import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
+import { addError, addSuccess } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 
 type Props = {
   insight: InsightResponse;
@@ -91,10 +91,11 @@ export function FormReport(props: Props) {
         setLoading(false);
         if (status === 'success') {
           dispatch(updateInsight(data));
+          dispatch(addSuccess('Report updated successfully'));
           console.log('Report updated successfully');
         } else {
           console.error('Failed to update report:', data);
-          alert('Failed to update report.');
+          dispatch(addError('Failed to update alert'));
         }
       });
     } else {
@@ -103,11 +104,12 @@ export function FormReport(props: Props) {
         setLoading(false);
         if (status === 'success') {
           dispatch(updateInsight(data));
+          dispatch(addSuccess('Report created successfully'));
           console.log('Report created successfully');
           if (props.setActive) props.setActive(data.id);
         } else {
           console.error('Failed to create report:', data);
-          alert('Failed to create report.');
+          dispatch(addError('Failed to create alert'));
         }
       });
     }
@@ -115,7 +117,7 @@ export function FormReport(props: Props) {
 
   const handleDelete = async () => {
     if (!props.insight.id) {
-      alert('Cannot delete a report without an ID.');
+      dispatch(addError('Cannot delete a report without an ID.'));
       return;
     }
 
@@ -125,10 +127,11 @@ export function FormReport(props: Props) {
       if (status === 'success') {
         dispatch(deleteInsight({ id: props.insight.id }));
         props.setActive('');
+        dispatch(addSuccess('Report deleted successfully'));
         console.log('Report deleted successfully');
       } else {
         console.error('Failed to delete report:', data);
-        alert('Failed to delete report.');
+        dispatch(addError('Failed to delete report'));
       }
     });
   };
diff --git a/libs/shared/lib/insight-sharing/components/AddItem.tsx b/libs/shared/lib/insight-sharing/components/AddItem.tsx
index fe8f3f173..b207b9ec6 100644
--- a/libs/shared/lib/insight-sharing/components/AddItem.tsx
+++ b/libs/shared/lib/insight-sharing/components/AddItem.tsx
@@ -4,6 +4,7 @@ import { MonitorType as InsightType, MonitorType } from './Sidebar';
 import { useAppDispatch, useSessionCache } from '../../data-access';
 import { addInsight } from '../../data-access/store/insightSharingSlice';
 import { wsCreateInsight } from '../../data-access/broker/wsInsightSharing';
+import { addError, addSuccess,  } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 
 type Props = {
   setAdding: (val: false | MonitorType) => void;
@@ -19,7 +20,7 @@ export function AddItem(props: Props) {
 
   const handleSave = async () => {
     if (!name.trim()) {
-      alert('Please enter a name.');
+      dispatch(addError('Name is required'));
       return;
     }
 
@@ -38,9 +39,10 @@ export function AddItem(props: Props) {
         dispatch(addInsight(data));
         props.setActive(data.id);
         props.setAdding(false);
+        dispatch(addSuccess('Succesfully created ' + props.type));
       } else {
         console.error('Failed to create insight:', data);
-        alert('Failed to create new ' + props.type);
+        dispatch(addError('Failed to create new ' + props.type))
       }
     });
   };
-- 
GitLab