From 40b881fd48c3e689b16dc97d3e68c5fc2797f2cb Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:11:03 +0200 Subject: [PATCH 1/6] feat: Added new mock data --- __mock__/team-communication.json | 3 +-- __mock__/team-noise-levels.json | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 __mock__/team-noise-levels.json diff --git a/__mock__/team-communication.json b/__mock__/team-communication.json index 6536683..23b6464 100644 --- a/__mock__/team-communication.json +++ b/__mock__/team-communication.json @@ -3,8 +3,7 @@ "title": "Team Communication", "description": "This visualization showcases the conversations between team members.", "sensors": { - "sensor-1": { - "title": "Slack", + "slack": { "value": { "U01HR3WBQH4": { "characters": 1868, diff --git a/__mock__/team-noise-levels.json b/__mock__/team-noise-levels.json new file mode 100644 index 0000000..742fa6e --- /dev/null +++ b/__mock__/team-noise-levels.json @@ -0,0 +1,19 @@ +{ + "team-noise-levels": { + "title": "Team Noise Levels", + "description": "This is a mock data for team noise levels", + "sensors": { + "meeting-room-nw": { + "title": "Meeting Room NW", + "description": "This is a mock data for meeting room NW", + "value": 50 + }, + "meeting-room-ne": { + "title": "Meeting Room NE", + "description": "This is a mock data for meeting room NE", + "value": 30 + } + + } + } +} \ No newline at end of file From 31ca58e6fd34b889955161efa794b84ccb86f75f Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:11:42 +0200 Subject: [PATCH 2/6] refactore: Refactored Types --- types/firebase.ts | 26 +++----------------------- types/team-activity.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 types/team-activity.ts diff --git a/types/firebase.ts b/types/firebase.ts index 5520869..40881a1 100644 --- a/types/firebase.ts +++ b/types/firebase.ts @@ -1,5 +1,7 @@ -export type ConfigType = 'ANALOG' | 'DIGITAL'; +import { TeamCommunicationData } from './team-activity'; +//General Modular Types across all visualizations +export type ConfigType = 'ANALOG' | 'DIGITAL'; export type DisplayType = keyof DisplayDataType; export interface Metric { @@ -54,25 +56,3 @@ export interface DisplayDataBase { export interface NoiseLevelsData { db: number; } - -//team-communication -export interface TeamCommunicationData { - [key: string]: TeamMember; -} - -export interface TeamMember { - id: string; - characters: number; - messages: number; - name: string; - color: string; - lastMessage: number; - lastReaction: string; - reactions: number; - connections: { - [key: string]: { - messages: number; - characters: number; - }; - }; -} diff --git a/types/team-activity.ts b/types/team-activity.ts new file mode 100644 index 0000000..041c22b --- /dev/null +++ b/types/team-activity.ts @@ -0,0 +1,26 @@ +//Team Communication +export interface TeamCommunicationData { + [key: string]: TeamMember; + } + + export interface TeamMember { + id: string; + characters: number; + messages: number; + name: string; + color: string; + lastMessage: number; + lastReaction: string; + reactions: number; + connections: { + [key: string]: { + messages: number; + characters: number; + }; + }; + } + + export type TeamCommunicationRecordTypes = 'most-messages-characters' | 'most-reactions' | 'least-messages-characters' | 'least-reactions' | 'most-connections' + export type TeamCommunicationRecord = { + type: TeamCommunicationRecordTypes; + } & TeamMember From ba221c296b92e996ea3bca0173fc575990ac2619 Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:11:57 +0200 Subject: [PATCH 3/6] feat: updated firebase functions --- functions/src/index.ts | 81 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index 2e87dca..4852d0c 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -1,8 +1,12 @@ +import {Bucket} from "@google-cloud/storage"; import * as admin from "firebase-admin"; import * as functions from "firebase-functions"; import * as puppeteer from "puppeteer"; -import * as path from "path"; -import {Bucket} from "@google-cloud/storage"; +import { + RTDBData, + TeamCommunicationData, + TeamCommunicationEntry, +} from "./types/firebase-types"; admin.initializeApp(); @@ -69,6 +73,15 @@ exports.clearRealtimeDatabase = functions.pubsub console.log(`Screenshot saved at: ${screenshotFilePath}`); const ref = rtdb.ref(pathToClear); + const data = await ref.once("value"); + const dataValue = data.val(); + const preparedTeamCommunicationsData = + prepareTeamCommunicationSync(dataValue); + preparedTeamCommunicationsData.forEach(async (entry) => { + await syncTeamMembersData(entry, "team-communication"); + }); + + await ref.remove(); await initializeRealtimeDatabase(); @@ -121,3 +134,67 @@ const initializeRealtimeDatabase = async () => { }); }); }; + +const prepareTeamCommunicationSync = +(data: RTDBData) => { + const preparedData: TeamCommunicationEntry[] = []; + + const dataSource = data["team-communication"]; + for (const sensorKey in dataSource.sensors) { + if (Object.prototype.hasOwnProperty + .call(dataSource.sensors, sensorKey)) { + const sensorData = dataSource.sensors[sensorKey].value; + for (const key in sensorData) { + if (Object.prototype.hasOwnProperty + .call(sensorData, key)) { + const incomingMember = sensorData[key]; + + // Check if the member with the same id already exists + const existingMemberIndex = + preparedData.findIndex((member) => member.id === incomingMember.id); + + if (existingMemberIndex !== -1) { + const existingMember = preparedData[existingMemberIndex]; + + preparedData[existingMemberIndex] = { + id: incomingMember.id, + timestamp: Date.now(), + connections: + (Object.keys(existingMember.connections).length || 0) + + (Object.keys(incomingMember.connections).length || 0), + characters: (existingMember.characters || 0) + + (incomingMember.characters || 0), + messages: (existingMember.messages || 0)+ + (incomingMember.messages || 0), + reactions: (existingMember.reactions || 0)+ + (incomingMember.reactions || 0), + }; + } else { + preparedData.push({ + id: incomingMember.id, + timestamp: Date.now(), + connections: Object.keys(incomingMember.connections).length || 0, + characters: incomingMember.characters || 0, + messages: incomingMember.messages || 0, + reactions: incomingMember.reactions || 0, + }); + } + } + } + } + } + + return preparedData; +}; + +const syncTeamMembersData = async ( + entry: TeamCommunicationEntry, + stat: string, +) => { + const userRef = firestore + .collection("users") + .doc(entry.id) + .collection(`${stat}-stats`) + .doc(`stat-${entry.id}`); + await userRef.set(entry); +}; From b8048ef1361d24b5e9d7e83208376ad54204e189 Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:15:41 +0200 Subject: [PATCH 4/6] fix: IXT Logo View & Collision Prevention --- utils/general.ts | 507 +++++++++++++++++++++-------------------------- 1 file changed, 227 insertions(+), 280 deletions(-) diff --git a/utils/general.ts b/utils/general.ts index 211d1fc..34077f4 100644 --- a/utils/general.ts +++ b/utils/general.ts @@ -4,8 +4,9 @@ import { DisplayDataMapping, FormattedDataMapping, } from '@/contexts/display-context'; -import { DisplayType, TeamMember, Views } from '@/types/firebase'; +import { DisplayType, Views } from '@/types/firebase'; import { Bound, MediaQueries } from '@/types/general'; +import { TeamMember } from '@/types/team-activity'; import { adjustToMedia } from './styles'; export const formatRTDBData = >( @@ -115,73 +116,115 @@ export const viewTeamPositions = ({ const centerX = spaces.width / 2; const centerY = spaces.height / 2; const space = Math.min(spaces.width, spaces.height); - const margin = adjustToMedia(query, [200, 300, 400]); - const step = (space - margin) / (members.length / 2); // Base step size + const margin = adjustToMedia(query, [50, 100, 200]); - return members.map((member, i) => { - const diagonal = i < members.length / 2 ? 'positive' : 'negative'; + const diagLength = space - margin * 2; + + const outerOffset = 50; // Space between end and last point + + const half = Math.ceil(members.length / 2); + const positiveDiagonalMembers = members.slice(0, half); + const negativeDiagonalMembers = members.slice(half); + + const positions: Node[] = []; + + /*** Positive Diagonal (\) - Full length, Blue ***/ + const x0_pos = centerX - diagLength / 2 + outerOffset; + const y0_pos = centerY - diagLength / 2 + outerOffset; + const x1_pos = centerX + diagLength / 2 - outerOffset; + const y1_pos = centerY + diagLength / 2 - outerOffset; - if (diagonal === 'positive') { - // Positive diagonal (\) - return { - ...member, - color: options?.offBranding ? member.color : '#003B5C', + const n_pos = positiveDiagonalMembers.length; - x: centerX - (members.length / 4) * step + i * step, - y: centerY - (members.length / 4) * step + i * step, - }; + for (let i = 0; i < n_pos; i++) { + const member = positiveDiagonalMembers[i]; + + let x, y; + + if (n_pos === 1) { + x = (x0_pos + x1_pos) / 2; + y = (y0_pos + y1_pos) / 2; } else { - // Negative diagonal (/) - const index = i - members.length / 2; - const totalNegativePoints = members.length / 2; - const threshold = Math.floor(totalNegativePoints * 0.4); // 40% of the negative diagonal - - if (index === threshold) { - // If the point is exactly at the 40% mark, increase the space - const largerStep = step * 2; // Increase step size at 40% - return { - ...member, - color: options?.offBranding ? member.color : '#003B5C', - x: - centerX + - (members.length / 4) * step - - (index * step + largerStep), // Larger space at 40% - y: - centerY - - (members.length / 4) * step + - (index * step + largerStep), // Larger space at 40% - }; - } else if (index > threshold) { - // For the points after 40%, adjust the spacing to account for the larger step at the 40% mark - const remainingPoints = totalNegativePoints - threshold; - const adjustedStep = (space - margin - step * 4) / remainingPoints; // Adjusted step after 40% - - const adjustedIndex = index - threshold; // Adjust index after 40% - return { - ...member, - color: options?.offBranding ? member.color : '#003B5C', - x: - centerX + - (members.length / 4) * step - - threshold * step - - adjustedIndex * adjustedStep, // Adjusted position - y: - centerY - - (members.length / 4) * step + - threshold * step + - adjustedIndex * adjustedStep, // Adjusted position - }; - } else { - // Regular step for points before 40% - return { - ...member, - color: options?.offBranding ? member.color : '#FF5555', - x: centerX + (members.length / 4) * step - index * step, // Regular step before 40% - y: centerY - (members.length / 4) * step + index * step, // Regular step before 40% - }; - } + const t = i / (n_pos - 1); + x = x0_pos + t * (x1_pos - x0_pos); + y = y0_pos + t * (y1_pos - y0_pos); } - }); + + positions.push({ + ...member, + x: x, + y: y, + color: options?.offBranding ? member.color : '#003B5C', // Blue color + } as Node); + } + + /*** Negative Diagonal (/) - Split into Upper (Red) and Lower (Blue) segments ***/ + const n_neg = negativeDiagonalMembers.length; + const n_upper = Math.ceil(n_neg * 0.4); // Approx. 40% of members for the upper segment + const n_lower = n_neg - n_upper; + + const upperSegmentMembers = negativeDiagonalMembers.slice(0, n_upper); + const lowerSegmentMembers = negativeDiagonalMembers.slice(n_upper); + + // Upper Segment (Red) - From top right, half-length towards center + const x0_neg_upper = centerX + diagLength / 2 - outerOffset; + const y0_neg_upper = centerY - diagLength / 2 + outerOffset; + const x_center = centerX; + const y_center = centerY; + const x1_neg_upper = x0_neg_upper + (x_center - x0_neg_upper) * 0.5; + const y1_neg_upper = y0_neg_upper + (y_center - y0_neg_upper) * 0.5; + + for (let i = 0; i < n_upper; i++) { + const member = upperSegmentMembers[i]; + + let x, y; + + if (n_upper === 1) { + x = (x0_neg_upper + x1_neg_upper) / 2; + y = (y0_neg_upper + y1_neg_upper) / 2; + } else { + const t = i / (n_upper - 1); + x = x0_neg_upper + t * (x1_neg_upper - x0_neg_upper); + y = y0_neg_upper + t * (y1_neg_upper - y0_neg_upper); + } + + positions.push({ + ...member, + x: x, + y: y, + color: options?.offBranding ? member.color : '#FF5555', // Red color + } as Node); + } + + // Lower Segment (Blue) - From bottom left up to the center + const x0_neg_lower = centerX - diagLength / 2 + outerOffset; + const y0_neg_lower = centerY + diagLength / 2 - outerOffset; + const x1_neg_lower = centerX; + const y1_neg_lower = centerY; + + for (let i = 0; i < n_lower; i++) { + const member = lowerSegmentMembers[i]; + + let x, y; + + if (n_lower === 1) { + x = (x0_neg_lower + x1_neg_lower) / 2; + y = (y0_neg_lower + y1_neg_lower) / 2; + } else { + const t = i / (n_lower - 1); + x = x0_neg_lower + t * (x1_neg_lower - x0_neg_lower); + y = y0_neg_lower + t * (y1_neg_lower - y0_neg_lower); + } + + positions.push({ + ...member, + x: x, + y: y, + color: options?.offBranding ? member.color : '#003B5C', // Blue color + } as Node); + } + + return positions; }, }; @@ -194,28 +237,136 @@ export interface Spaces { freeSpaceArea: Bound[]; } +function circleRectOverlap(node: Node, rect: Bound): boolean { + // Find the closest point to the circle within the rectangle + const closestX = clamp(node.x, rect.x, rect.x + rect.width); + const closestY = clamp(node.y, rect.y, rect.y + rect.height); + + // Calculate the distance between the circle's center and this closest point + const dx = node.x - closestX; + const dy = node.y - closestY; + + // If the distance is less than the circle's radius, an intersection occurs + const distanceSquared = dx * dx + dy * dy; + + return distanceSquared < node.radius * node.radius; +} + +function clamp(value: number, min: number, max: number): number { + return Math.max(min, Math.min(max, value)); +} + +// Implementing resolveFreeSpaceCollisions +export const resolveFreeSpaceCollisions = ( + nodes: Node[], + spaces: Spaces +): Node[] => { + const adjustedNodes = nodes.map((node) => { + const adjustedNode = { ...node }; + let overlapFound = true; + const maxIterations = 10; + let iterations = 0; + + while (overlapFound && iterations < maxIterations) { + overlapFound = false; + + for (const bound of spaces.freeSpaceArea) { + if (circleRectOverlap(adjustedNode, bound)) { + // Adjust node position + // Move node away from the rectangle + const dx = adjustedNode.x - (bound.x + bound.width / 2); + const dy = adjustedNode.y - (bound.y + bound.height / 2); + const distance = Math.sqrt(dx * dx + dy * dy); + const moveDistance = adjustedNode.radius; + + if (distance > 0) { + adjustedNode.x += (dx / distance) * moveDistance; + adjustedNode.y += (dy / distance) * moveDistance; + } else { + // Randomly move the node + adjustedNode.x += Math.random() * moveDistance * 2 - moveDistance; + adjustedNode.y += Math.random() * moveDistance * 2 - moveDistance; + } + + overlapFound = true; + break; // Recheck all bounds after adjusting + } + } + + iterations++; + } + + return adjustedNode; + }); + + return adjustedNodes; +}; + +// Implementing resolveNodeCollisions +export const resolveNodeCollisions = (nodes: Node[]): Node[] => { + const adjustedNodes = nodes.map((node) => ({ ...node })); + + const maxIterations = 10; + for (let iteration = 0; iteration < maxIterations; iteration++) { + let collisionFound = false; + + for (let i = 0; i < adjustedNodes.length; i++) { + for (let j = i + 1; j < adjustedNodes.length; j++) { + const nodeA = adjustedNodes[i]; + const nodeB = adjustedNodes[j]; + + const dx = nodeB.x - nodeA.x; + const dy = nodeB.y - nodeA.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const minDistance = nodeA.radius + nodeB.radius; + + if (distance < minDistance) { + // Nodes are overlapping + collisionFound = true; + + // Calculate overlap amount + const overlap = (minDistance - distance) / 2; + + // Normalize the vector between nodes + const nx = dx / distance; + const ny = dy / distance; + + // Displace nodes away from each other + nodeA.x -= nx * overlap; + nodeA.y -= ny * overlap; + nodeB.x += nx * overlap; + nodeB.y += ny * overlap; + } + } + } + + if (!collisionFound) { + break; + } + } + + return adjustedNodes; +}; + +// Updating resolveCollisions to include the new functions export const resolveCollisions = ( spaces: Spaces, nodes: Node[], query: MediaQueries ): Node[] => { - // Step 1 - Check for viewport collisions and return the updated nodes + // Step 1 - Check for viewport collisions const viewPortResolvedNodes = resolveViewportCollisions(spaces, query, nodes); - // Step 2 - Check for free space collisions and return the updated nodes - // const freeSpaceResolvedNodes = resolveFreeSpaceCollisions( - // viewPortResolvedNodes, - // spaces, - // query - // ); + // Step 2 - Check for free space collisions + const freeSpaceResolvedNodes = resolveFreeSpaceCollisions( + viewPortResolvedNodes, + spaces + ); - // // Step 3 - Check for node collisions between each node - // const finalResolvedNodes = resolveNodeCollisions( - // freeSpaceResolvedNodes, - // query - // ); + // Step 3 - Check for node collisions between each node + const finalResolvedNodes = resolveNodeCollisions(freeSpaceResolvedNodes); - return viewPortResolvedNodes; + return finalResolvedNodes; }; export const resolveViewportCollisions = ( @@ -254,210 +405,6 @@ export const resolveViewportCollisions = ( return resolvedNodes; }; -// export const resolveFreeSpaceCollisions = ( -// nodes: Node[], -// spaces: Spaces, -// query: MediaQueries -// ): Node[] => { -// const radiusPadding = adjustToMedia(query, RADIUS_PADDING); - -// const resolvedNodes = nodes.map((node) => { -// const intersections: { x: number; y: number }[] = []; -// // Cast rays in 4 directions (left, right, up, down) -// const rays = [ -// { dx: -1, dy: 0 }, // Left -// { dx: 1, dy: 0 }, // Right -// { dx: 0, dy: -1 }, // Up -// { dx: 0, dy: 1 }, // Down -// ]; - -// rays.forEach((ray) => { -// const rayEnd = castRay(node, ray, spaces); -// if (rayEnd) intersections.push(rayEnd); -// }); - -// if (intersections.length === 0) return node; - -// const chosenIntersection = -// intersections[Math.floor(Math.random() * intersections.length)]; - -// const adjustedNode = { -// ...node, -// x: chosenIntersection.x + node.radius + radiusPadding, -// y: chosenIntersection.y + node.radius + radiusPadding, -// }; - -// return adjustedNode; -// }); - -// return resolvedNodes; -// }; - -// const isInside = ( -// edges: [number[], number[]][], -// xp: number, -// yp: number -// ): boolean => { -// let cnt = 0; - -// edges.forEach((edge) => { -// const [[x1, y1], [x2, y2]] = edge; - -// if (yp < y1 !== yp < y2 && xp < x1 + ((yp - y1) / (y2 - y1)) * (x2 - x1)) { -// cnt++; -// } -// }); - -// return cnt % 2 === 1; -// }; - -// const castRay = ( -// node: Node, -// ray: Ray, -// spaces: Spaces -// ): { x: number; y: number } | null => { -// let rayX = node.x; -// let rayY = node.y; - -// const getEdgesFromRect = (rect: Bound): [number[], number[]][] => [ -// [ -// [rect.left, rect.top], -// [rect.right, rect.top], -// ], // Top edge -// [ -// [rect.right, rect.top], -// [rect.right, rect.bottom], -// ], // Right edge -// [ -// [rect.right, rect.bottom], -// [rect.left, rect.bottom], -// ], // Bottom edge -// [ -// [rect.left, rect.bottom], -// [rect.left, rect.top], -// ], // Left edge -// ]; - -// while (true) { -// rayX += ray.dx; -// rayY += ray.dy; - -// for (const rect of spaces.freeSpaceArea) { -// const edges = getEdgesFromRect(rect); -// if (isInside(edges, rayX, rayY)) { -// return { x: rayX, y: rayY }; -// } -// } - -// if (rayX < 0 || rayY < 0 || rayX > spaces.width || rayY > spaces.height) { -// break; -// } -// } - -// return null; -// }; - -// export const resolveNodeCollisions = ( -// nodes: Node[], -// query: MediaQueries -// ): Node[] => { -// const radiusPadding = adjustToMedia(query, RADIUS_PADDING); -// const resolvedNodes = [...nodes]; - -// for (let i = 0; i < resolvedNodes.length; i++) { -// for (let j = i + 1; j < resolvedNodes.length; j++) { -// const node1 = resolvedNodes[i]; -// const node2 = resolvedNodes[j]; - -// const dx = node2.x - node1.x; -// const dy = node2.y - node1.y; -// const distance = Math.sqrt(dx * dx + dy * dy); - -// const minDistance = node1.radius + node2.radius + 2 * radiusPadding; - -// if (distance < minDistance) { -// const rays = [ -// { dx: -1, dy: 0 }, // Left -// { dx: 1, dy: 0 }, // Right -// { dx: 0, dy: -1 }, // Up -// { dx: 0, dy: 1 }, // Down -// ]; - -// const intersections1 = castRaysForCollisions(node1, rays, nodes); -// const intersections2 = castRaysForCollisions(node2, rays, nodes); - -// if (intersections1.length > 0) { -// const chosenIntersection1 = -// intersections1[Math.floor(Math.random() * intersections1.length)]; -// resolvedNodes[i] = { -// ...node1, -// x: chosenIntersection1.x + node1.radius + radiusPadding, -// y: chosenIntersection1.y + node1.radius + radiusPadding, -// }; -// } - -// if (intersections2.length > 0) { -// const chosenIntersection2 = -// intersections2[Math.floor(Math.random() * intersections2.length)]; -// resolvedNodes[j] = { -// ...node2, -// x: chosenIntersection2.x + node2.radius + radiusPadding, -// y: chosenIntersection2.y + node2.radius + radiusPadding, -// }; -// } -// } -// } -// } - -// return resolvedNodes; -// }; -// const castRaysForCollisions = ( -// node: Node, -// rays: { dx: number; dy: number }[], -// nodes: Node[] -// ): { x: number; y: number }[] => { -// const intersections: { x: number; y: number }[] = []; - -// rays.forEach((ray) => { -// let rayX = node.x; -// let rayY = node.y; - -// while (true) { -// rayX += ray.dx; -// rayY += ray.dy; - -// let collisionFound = false; - -// for (const otherNode of nodes) { -// if (otherNode !== node) { -// const dx = otherNode.x - rayX; -// const dy = otherNode.y - rayY; -// const distance = Math.sqrt(dx * dx + dy * dy); - -// if (distance < otherNode.radius) { -// intersections.push({ x: rayX, y: rayY }); -// collisionFound = true; -// break; -// } -// } -// } - -// if (collisionFound) break; - -// if ( -// rayX < 0 || -// rayY < 0 || -// rayX > window.innerWidth || -// rayY > window.innerHeight -// ) { -// break; -// } -// } -// }); - -// return intersections; -// }; - export const calculatePreferredWhiteSpace = ( width: number, height: number, From 00969f8098cb4fafeeecf66e737f5d067cf6c222 Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:20:20 +0200 Subject: [PATCH 5/6] fix: Preventing re-render on nodes change --- .../team-communication/TeamCommunication.tsx | 59 ++++++++++++++----- .../team-communication/internal/TeamNodes.tsx | 2 +- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/components/team-communication/TeamCommunication.tsx b/components/team-communication/TeamCommunication.tsx index 6b957a8..a5b5bc4 100644 --- a/components/team-communication/TeamCommunication.tsx +++ b/components/team-communication/TeamCommunication.tsx @@ -3,7 +3,7 @@ import { useConfig } from '@/contexts/config-context'; import { useDisplay } from '@/contexts/display-context'; import { useMedia } from '@/hooks/use-media'; import { useWindowDimensions } from '@/hooks/use-window-dimensions'; -import { TeamMember } from '@/types/firebase'; +import { TeamMember } from '@/types/team-activity'; import { calculatePreferredWhiteSpace, calulcateAvailableSpace, @@ -11,7 +11,7 @@ import { relativeTeamCommunicationMemberSize, viewTeamPositions, } from '@/utils/general'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import TeamDefs from './internal/TeamDefs'; import TeamEdges from './internal/TeamEdges'; import TeamNodes from './internal/TeamNodes'; @@ -30,7 +30,7 @@ export interface Edge { } const TeamCommunication = () => { - //Contexts + // Contexts const { displayData, freeSpaceArea } = useDisplay<'team-communication'>(); const { view } = useChart(); const { metricConfigs } = useConfig(); @@ -54,15 +54,14 @@ const TeamCommunication = () => { const delayBetweenInstance = 0.2; - //Only Support Slack const slackData = displayData?.sensors[0].value; - const oldSlackDataRef = useRef(slackData); // Edges & Nodes const [edges, setEdges] = useState([]); const [nodes, setNodes] = useState([]); + const [positions, setPositions] = useState([]); - //Custom Branding + // Custom Branding const [turnOffBranding, setTurnOffBranding] = useState(false); useEffect(() => { @@ -76,9 +75,9 @@ const TeamCommunication = () => { } }, [view]); + // First useEffect: Update positions only when view changes or on initial render useEffect(() => { if (slackData) { - // First, calculate the radius for each node based on messages const totalMessages = slackData.reduce( (acc, value) => acc + value.messages, 0 @@ -94,13 +93,13 @@ const TeamCommunication = () => { return { ...value, - radius, // Add radius first + radius, }; }); const positionPreparedData = viewTeamPositions({ view, - members: updatedNodes, // Pass nodes with radius + members: updatedNodes, spaces: { width, height, @@ -110,13 +109,43 @@ const TeamCommunication = () => { options: { offBranding: turnOffBranding }, }); - // Update the nodes with the new positions - setNodes(positionPreparedData); + setPositions(positionPreparedData); + } + }, [view, width, height, freeSpaceArea, query, turnOffBranding]); + + // Second useEffect: Update other node properties when data changes + useEffect(() => { + if (positions.length > 0 && slackData) { + const totalMessages = slackData.reduce( + (acc, value) => acc + value.messages, + 0 + ); + + const updatedNodes = positions.map((node) => { + const matchingValue = slackData.find((value) => value.id === node.id); + if (matchingValue) { + const radius = relativeTeamCommunicationMemberSize( + matchingValue.messages, + availableSpace, + totalMessages, + defaultMemberSize + ); + + return { + ...node, + ...matchingValue, + radius, + }; + } else { + return node; + } + }); - oldSlackDataRef.current = slackData; + setNodes(updatedNodes); } - }, [slackData, view, width, height, freeSpaceArea, query]); + }, [slackData, positions, availableSpace, defaultMemberSize]); + // Update edges when nodes change useEffect(() => { const newEdges: Edge[] = []; nodes.forEach((node, i) => { @@ -128,7 +157,7 @@ const TeamCommunication = () => { const matchingKey = nodeKeys.filter((key) => targetKeys.includes(key) ); - if (matchingKey) { + if (matchingKey.length > 0) { newEdges.push({ source: node, target: targetNode, @@ -150,7 +179,7 @@ const TeamCommunication = () => { nodes={nodes} /> diff --git a/components/team-communication/internal/TeamNodes.tsx b/components/team-communication/internal/TeamNodes.tsx index ab97047..b2c1fd5 100644 --- a/components/team-communication/internal/TeamNodes.tsx +++ b/components/team-communication/internal/TeamNodes.tsx @@ -3,7 +3,6 @@ import { useChart } from '@/contexts/chart-context'; import { useInitialRender } from '@/hooks/use-init-render'; import { useMedia } from '@/hooks/use-media'; import { useWindowDimensions } from '@/hooks/use-window-dimensions'; -import { TeamMember } from '@/types/firebase'; import { adjustToMedia, determineTooltipPosition, @@ -14,6 +13,7 @@ import { easeInOut, motion } from 'framer-motion'; import { FC, useEffect, useState } from 'react'; import { Node } from '../TeamCommunication'; import Tooltip from './Tooltip'; +import { TeamMember } from '@/types/team-activity'; interface ITeamNodeProps { nodes: Node[]; From d6e6b2cae10bfce3d055a2cad5fd6705b35b8081 Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:21:45 +0200 Subject: [PATCH 6/6] fix: Fixed type import --- types/data.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/data.ts b/types/data.ts index b03af27..68f4b65 100644 --- a/types/data.ts +++ b/types/data.ts @@ -1,4 +1,5 @@ -import { NoiseLevelsData, TeamMember } from './firebase'; +import { NoiseLevelsData } from './firebase'; +import { TeamMember } from './team-activity'; export interface FormtattedDataBaseType { 'team-communication': FormattedDataBase;