expand sub tasks

This commit is contained in:
chamiakJ
2025-07-03 01:31:05 +05:30
parent 3bef18901a
commit ecd4d29a38
435 changed files with 13150 additions and 11087 deletions

View File

@@ -1,4 +1,17 @@
import { Button, ConfigProvider, Flex, Form, Mentions, Skeleton, Space, Tooltip, Typography, Dropdown, Menu, Popconfirm } from 'antd';
import {
Button,
ConfigProvider,
Flex,
Form,
Mentions,
Skeleton,
Space,
Tooltip,
Typography,
Dropdown,
Menu,
Popconfirm,
} from 'antd';
import { useEffect, useState, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import DOMPurify from 'dompurify';
@@ -24,13 +37,10 @@ const MAX_COMMENT_LENGTH = 2000;
const urlRegex = /((https?:\/\/|www\.)[\w\-._~:/?#[\]@!$&'()*+,;=%]+)/gi;
function linkify(text: string): string {
return text.replace(
urlRegex,
url => {
const href = url.startsWith('http') ? url : `https://${url}`;
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${url}</a>`;
}
);
return text.replace(urlRegex, url => {
const href = url.startsWith('http') ? url : `https://${url}`;
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${url}</a>`;
});
}
const ProjectViewUpdates = () => {
@@ -54,7 +64,14 @@ const ProjectViewUpdates = () => {
if (!projectId) return;
try {
setIsLoading(true);
const res = await projectCommentsApiService.getMentionMembers(projectId, 1, 15, null, null, null);
const res = await projectCommentsApiService.getMentionMembers(
projectId,
1,
15,
null,
null,
null
);
if (res.done) {
setMembers(res.body as IMentionMemberViewModel[]);
}
@@ -85,7 +102,7 @@ const ProjectViewUpdates = () => {
try {
setIsSubmitting(true);
if (!commentValue) {
console.error('Comment content is empty');
return;
@@ -95,7 +112,7 @@ const ProjectViewUpdates = () => {
project_id: projectId,
team_id: getUserSession()?.team_id,
content: commentValue.trim(),
mentions: selectedMembers
mentions: selectedMembers,
};
const res = await projectCommentsApiService.createProjectComment(body);
@@ -107,8 +124,11 @@ const ProjectViewUpdates = () => {
created_by: getUserSession()?.name || '',
created_at: new Date().toISOString(),
content: commentValue.trim(),
mentions: (res.body as IProjectUpdateCommentViewModel).mentions ?? [undefined, undefined],
}
mentions: (res.body as IProjectUpdateCommentViewModel).mentions ?? [
undefined,
undefined,
],
},
]);
handleCancel();
}
@@ -132,16 +152,18 @@ const ProjectViewUpdates = () => {
setSelectedMembers([]);
}, [form]);
const mentionsOptions = useMemo(() =>
members?.map(member => ({
value: member.id,
label: member.name,
})) ?? [], [members]
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
@@ -188,30 +210,36 @@ const ProjectViewUpdates = () => {
}
}, []);
const configProviderTheme = useMemo(() => ({
components: {
Button: {
defaultColor: colors.lightGray,
defaultHoverColor: colors.darkGray,
const configProviderTheme = useMemo(
() => ({
components: {
Button: {
defaultColor: colors.lightGray,
defaultHoverColor: colors.darkGray,
},
},
},
}), []);
}),
[]
);
// Context menu for each comment (memoized)
const getCommentMenu = useCallback((commentId: string) => (
<Menu>
<Menu.Item key="delete">
<Popconfirm
title="Are you sure you want to delete this comment?"
onConfirm={() => handleDeleteComment(commentId)}
okText="Yes"
cancelText="No"
>
Delete
</Popconfirm>
</Menu.Item>
</Menu>
), [handleDeleteComment]);
const getCommentMenu = useCallback(
(commentId: string) => (
<Menu>
<Menu.Item key="delete">
<Popconfirm
title="Are you sure you want to delete this comment?"
onConfirm={() => handleDeleteComment(commentId)}
okText="Yes"
cancelText="No"
>
Delete
</Popconfirm>
</Menu.Item>
</Menu>
),
[handleDeleteComment]
);
const renderComment = useCallback(
(comment: IProjectUpdateCommentViewModel) => {
@@ -224,7 +252,7 @@ const ProjectViewUpdates = () => {
<Dropdown
key={comment.id ?? ''}
overlay={getCommentMenu(comment.id ?? '')}
trigger={["contextMenu"]}
trigger={['contextMenu']}
>
<div>
<Flex gap={8}>
@@ -240,7 +268,10 @@ const ProjectViewUpdates = () => {
</Typography.Text>
</Tooltip>
</Space>
<Typography.Paragraph style={{ margin: '8px 0' }} ellipsis={{ rows: 3, expandable: true }}>
<Typography.Paragraph
style={{ margin: '8px 0' }}
ellipsis={{ rows: 3, expandable: true }}
>
<div
className={`mentions-${themeClass}`}
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
@@ -256,18 +287,12 @@ const ProjectViewUpdates = () => {
[theme, configProviderTheme, handleDeleteComment, handleCommentLinkClick]
);
const commentsList = useMemo(() =>
comments.map(renderComment), [comments, renderComment]
);
const commentsList = useMemo(() => comments.map(renderComment), [comments, renderComment]);
return (
<Flex gap={24} vertical>
<Flex vertical gap={16}>
{isLoadingComments ? (
<Skeleton active />
) : (
commentsList
)}
{isLoadingComments ? <Skeleton active /> : commentsList}
</Flex>
<Form onFinish={handleAddComment}>