diff --git a/worklenz-frontend/src/pages/projects/project-view-1/updates/project-view-updates.tsx b/worklenz-frontend/src/pages/projects/project-view-1/updates/project-view-updates.tsx
index 684cb5f9..dbf66036 100644
--- a/worklenz-frontend/src/pages/projects/project-view-1/updates/project-view-updates.tsx
+++ b/worklenz-frontend/src/pages/projects/project-view-1/updates/project-view-updates.tsx
@@ -1,5 +1,5 @@
import { Button, ConfigProvider, Flex, Form, Mentions, Skeleton, Space, Tooltip, Typography } from 'antd';
-import { useEffect, useState, useCallback } from 'react';
+import { useEffect, useState, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import DOMPurify from 'dompurify';
import { useParams } from 'react-router-dom';
@@ -68,7 +68,7 @@ const ProjectViewUpdates = () => {
}
}, [projectId]);
- const handleAddComment = async () => {
+ const handleAddComment = useCallback(async () => {
if (!projectId || characterLength === 0) return;
try {
@@ -96,15 +96,13 @@ const ProjectViewUpdates = () => {
} finally {
setIsSubmitting(false);
setCommentValue('');
-
-
}
- };
+ }, [projectId, characterLength, commentValue, selectedMembers, getComments]);
useEffect(() => {
void getMembers();
void getComments();
- }, [getMembers, getComments,refreshTimestamp]);
+ }, [getMembers, getComments, refreshTimestamp]);
const handleCancel = useCallback(() => {
form.resetFields(['comment']);
@@ -113,14 +111,16 @@ const ProjectViewUpdates = () => {
setSelectedMembers([]);
}, [form]);
- const mentionsOptions =
+ const mentionsOptions = useMemo(() =>
members?.map(member => ({
value: member.id,
label: member.name,
- })) ?? [];
+ })) ?? [], [members]
+ );
const memberSelectHandler = useCallback((member: IMentionMemberSelectOption) => {
if (!member?.value || !member?.label) return;
+
setSelectedMembers(prev =>
prev.some(mention => mention.id === member.value)
? prev
@@ -131,13 +131,11 @@ const ProjectViewUpdates = () => {
const parts = prev.split('@');
const lastPart = parts[parts.length - 1];
const mentionText = member.label;
- // Keep only the part before the @ and add the new mention
return prev.slice(0, prev.length - lastPart.length) + mentionText;
});
}, []);
const handleCommentChange = useCallback((value: string) => {
- // Only update the value without trying to replace mentions
setCommentValue(value);
setCharacterLength(value.trim().length);
}, []);
@@ -157,56 +155,69 @@ const ProjectViewUpdates = () => {
[getComments]
);
+ const configProviderTheme = useMemo(() => ({
+ components: {
+ Button: {
+ defaultColor: colors.lightGray,
+ defaultHoverColor: colors.darkGray,
+ },
+ },
+ }), []);
+
+ const renderComment = useCallback((comment: IProjectUpdateCommentViewModel) => {
+ const sanitizedContent = DOMPurify.sanitize(comment.content || '');
+ const timeDifference = calculateTimeDifference(comment.created_at || '');
+ const themeClass = theme === 'dark' ? 'dark' : 'light';
+
+ return (
+
+
+
+
+
+ {comment.created_by || ''}
+
+
+
+ {timeDifference}
+
+
+
+
+
+
+
+ }
+ shape="circle"
+ type="text"
+ size='small'
+ onClick={() => handleDeleteComment(comment.id)}
+ />
+
+
+
+ );
+ }, [theme, configProviderTheme, handleDeleteComment]);
+
+ const commentsList = useMemo(() =>
+ comments.map(renderComment), [comments, renderComment]
+ );
+
return (
- {
- isLoadingComments ? (
-
- ):
- comments.map(comment => (
-
-
-
-
-
- {comment.created_by || ''}
-
-
-
- {calculateTimeDifference(comment.created_at || '')}
-
-
-
-
-
-
-
- }
- shape="circle"
- type="text"
- size='small'
- onClick={() => handleDeleteComment(comment.id)}
- />
-
-
-
- ))}
+ {isLoadingComments ? (
+
+ ) : (
+ commentsList
+ )}