diff --git a/src/components/Dashboard/Profile/sections/Comments/common/Autocomplete.tsx b/src/components/Dashboard/Profile/sections/Comments/common/Autocomplete.tsx index 4a4a80c8..88e9210a 100644 --- a/src/components/Dashboard/Profile/sections/Comments/common/Autocomplete.tsx +++ b/src/components/Dashboard/Profile/sections/Comments/common/Autocomplete.tsx @@ -7,7 +7,7 @@ import { getImageUrl } from "@/utils"; import getCaretCoordinates from "textarea-caret"; type Props = { - handleTagAdd: (userId: number, fullName: string) => void; + handleTagAdd: (userId: number, fullName: string, personId: number) => void; newCommentText: string; textAreaRef: React.RefObject; activeRowIndex: number; @@ -42,7 +42,7 @@ export default function Autocomplete({ const filteredUsers = useMemo(() => { if (userFilter === null) return; - return users?.filter((user) => user?.fullName?.toLowerCase().includes(userFilter)); + return users?.filter((user) => user?.fullName?.toLowerCase().includes(userFilter) && user?.personId != null); }, [userFilter, users]); useEffect(() => { @@ -55,7 +55,10 @@ export default function Autocomplete({ if (!filteredUsers) return; const activeUser = filteredUsers[activeRowIndex]; if (activeUser) { - setOnSelectTrigger(() => () => handleTagAdd(activeUser.id, activeUser.fullName.replaceAll(/ /g, ""))); + setOnSelectTrigger(() => () => { + if (activeUser.personId == null) return; + handleTagAdd(activeUser.id, activeUser.fullName.replaceAll(/ /g, ""), activeUser.personId); + }); } else { setOnSelectTrigger(null); } @@ -83,8 +86,8 @@ export default function Autocomplete({ } }, [activeRowIndex, filteredUsers]); - const handleUserSelect = (userId: number, fullName: string) => { - handleTagAdd(userId, fullName.replaceAll(/ /g, "")); + const handleUserSelect = (userId: number, fullName: string, personId: number) => { + handleTagAdd(userId, fullName.replaceAll(/ /g, ""), personId); }; const resolvedAvatarUrl = (url: string | null | undefined) => { @@ -127,7 +130,10 @@ export default function Autocomplete({ key={user.id} role="option" aria-selected={isActive} - onClick={() => handleUserSelect(user.id, user.fullName)} + onClick={() => { + if (user.personId == null) return; + handleUserSelect(user.id, user.fullName, user.personId); + }} style={{ backgroundColor: isActive ? "var(--editableField-optionRow-selectedBg)" : "transparent", cursor: "pointer", diff --git a/src/components/Dashboard/Profile/sections/Comments/common/Comment.tsx b/src/components/Dashboard/Profile/sections/Comments/common/Comment.tsx index 000ad904..8a2e5329 100644 --- a/src/components/Dashboard/Profile/sections/Comments/common/Comment.tsx +++ b/src/components/Dashboard/Profile/sections/Comments/common/Comment.tsx @@ -9,7 +9,6 @@ type EditState = { text: string; canSave: boolean; isUpdating: boolean; - isTagFetch: boolean; onTextChange: (text: string) => void; onKeyPress: (e: React.KeyboardEvent) => void; onSave: () => void; diff --git a/src/components/Dashboard/Profile/sections/Comments/common/CommentEdit.tsx b/src/components/Dashboard/Profile/sections/Comments/common/CommentEdit.tsx index 02291037..8b78617c 100644 --- a/src/components/Dashboard/Profile/sections/Comments/common/CommentEdit.tsx +++ b/src/components/Dashboard/Profile/sections/Comments/common/CommentEdit.tsx @@ -15,7 +15,6 @@ type EditState = { text: string; canSave: boolean; isUpdating: boolean; - isTagFetch: boolean; onTextChange: (text: string) => void; onKeyPress: (e: React.KeyboardEvent) => void; onSave: () => void; @@ -86,7 +85,7 @@ export function CommentEdit({ commentId, edit }: Props) { {t("dashboard.commentsSection.saveEdit")} diff --git a/src/components/Dashboard/Profile/sections/Comments/common/EntityComments.tsx b/src/components/Dashboard/Profile/sections/Comments/common/EntityComments.tsx index 8495ec7f..7d929512 100644 --- a/src/components/Dashboard/Profile/sections/Comments/common/EntityComments.tsx +++ b/src/components/Dashboard/Profile/sections/Comments/common/EntityComments.tsx @@ -14,7 +14,6 @@ import { useCommentMenu } from "./hooks/useCommentMenu"; import { AddCommentButton, Container, NewCommentSection, TagOverlay, TextArea } from "./styles"; import { useCommentTag } from "./hooks/useCommentTag"; import Autocomplete from "./Autocomplete"; -import { getPersonIds } from "./helpers"; type Props = { entityId: Id; @@ -27,7 +26,6 @@ export function EntityComments({ entityId, entityType, comments, testId }: Props const { t } = useTranslation(); const { mutate: createComment, isPending: isCreating } = useCreateComment(entityId, entityType); const [newCommentText, setNewCommentText] = useState(""); - const [isTagFetch, setIsTagFetch] = useState(false); const textAreaRef = useRef(null); const overlayRef = useRef(null); @@ -62,16 +60,15 @@ export function EntityComments({ entityId, entityType, comments, testId }: Props if (!newCommentText.trim()) return; let formattedText = newCommentText; - const taggedUserIds: number[] = []; + const taggedPersonIds: number[] = []; tags.forEach((tag) => { formattedText = formattedText.replace(`@${tag.name}`, `<@${tag.id}>`); - if (formattedText.includes(`<@${tag.id}>`) && !taggedUserIds.includes(tag.id)) { - taggedUserIds.push(tag.id); + if (formattedText.includes(`<@${tag.id}>`) && !taggedPersonIds.includes(tag.personId)) { + taggedPersonIds.push(tag.personId); } }); - const taggedPersonIds = await getPersonIds(taggedUserIds, setIsTagFetch, t); createComment( { text: formattedText.trim(), @@ -104,17 +101,16 @@ export function EntityComments({ entityId, entityType, comments, testId }: Props if (!edit.editText.trim() || !edit.editingCommentId) return; const currentTags = initTags(edit.editText); - const taggedUserIds: number[] = []; + const taggedPersonIds: number[] = []; let formattedText = edit.editText; currentTags?.forEach((tag) => { formattedText = formattedText.replace(`@${tag.name}`, `<@${tag.id}>`); - if (formattedText.includes(`<@${tag.id}>`) && !taggedUserIds.includes(tag.id)) { - taggedUserIds.push(tag.id); + if (formattedText.includes(`<@${tag.id}>`) && !taggedPersonIds.includes(tag.personId)) { + taggedPersonIds.push(tag.personId); } }); - const taggedPersonIds = await getPersonIds(taggedUserIds, setIsTagFetch, t); updateComment( { text: formattedText.trim(), taggedPersonIds }, @@ -154,7 +150,6 @@ export function EntityComments({ entityId, entityType, comments, testId }: Props text: edit.editText, canSave: edit.canSave, isUpdating, - isTagFetch, onTextChange: edit.updateEditText, onKeyPress: (e) => edit.handleKeyPress(e, handleSaveEdit), onSave: handleSaveEdit, @@ -203,7 +198,7 @@ export function EntityComments({ entityId, entityType, comments, testId }: Props {t("dashboard.commentsSection.addComment")} diff --git a/src/components/Dashboard/Profile/sections/Comments/common/helpers.ts b/src/components/Dashboard/Profile/sections/Comments/common/helpers.ts deleted file mode 100644 index c695b626..00000000 --- a/src/components/Dashboard/Profile/sections/Comments/common/helpers.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { apiPathUser } from "@/config/constants"; -import axios from "axios"; -import { TFunction } from "i18next"; -import { toast } from "react-toastify"; - -export const getPersonIds = async ( - taggedUserIds: number[], - setIsTagFetch: (arg: boolean) => void, - t: TFunction<"translation", undefined>, -) => { - if (!taggedUserIds.length) return; - const promiseArray = taggedUserIds.map(async (id) => { - setIsTagFetch(true); - try { - const response = await axios.get(`${apiPathUser}/${id}`); - return response.data.data.personId; - } catch (err) { - console.error(err); - toast.error(t("dashboard.commentsSection.errorTagging")); - return null; - } finally { - setIsTagFetch(false); - } - }); - const resolvedPersonIds = await Promise.all(promiseArray); - return resolvedPersonIds?.filter((id): id is number => id !== null); -}; diff --git a/src/components/Dashboard/Profile/sections/Comments/common/hooks/useCommentTag.tsx b/src/components/Dashboard/Profile/sections/Comments/common/hooks/useCommentTag.tsx index 6630ca5c..fa515502 100644 --- a/src/components/Dashboard/Profile/sections/Comments/common/hooks/useCommentTag.tsx +++ b/src/components/Dashboard/Profile/sections/Comments/common/hooks/useCommentTag.tsx @@ -8,7 +8,7 @@ export function useCommentTag( setNewCommentText?: (text: string) => void, textAreaRef?: React.RefObject | null, ) { - const [tags, setTags] = useState<{ id: number; name: string }[]>([]); + const [tags, setTags] = useState<{ id: number; name: string; personId: number }[]>([]); const [showAutocomplete, setShowAutocomplete] = useState(false); const [activeRowIndex, setActiveRowIndex] = useState(0); const [filteredListLength, setFilteredListLength] = useState(0); @@ -76,7 +76,7 @@ export function useCommentTag( return elements; }, [value, tags, users]); - const handleTagAdd = (userId: number, fullName: string) => { + const handleTagAdd = (userId: number, fullName: string, personId: number) => { if (!value || !textAreaRef?.current) return null; const cursorPosition = textAreaRef.current.selectionStart; const textBeforeCaret = value.substring(0, cursorPosition); @@ -85,7 +85,7 @@ export function useCommentTag( const newText = textBeforeCaret.substring(0, lastAtIndex) + `@${fullName} ` + textAfterCaret; setNewCommentText?.(newText); - setTags((prev) => [...prev, { id: userId, name: fullName }]); + setTags((prev) => [...prev, { id: userId, name: fullName, personId }]); setShowAutocomplete(false); }; @@ -112,7 +112,7 @@ export function useCommentTag( if (!text || !users) return text; return text.replace(/<@(\d+)>/g, (match, userId) => { const user = users.find((u) => u.id === Number(userId)); - return user ? `@${user.fullName.replaceAll(/ /g, "")}` : "@user"; + return user ? `@${user.fullName.replaceAll(/ /g, "")}` : `@user:${userId}`; }); }, [users], @@ -120,16 +120,16 @@ export function useCommentTag( const initTags = (value: string) => { if (!value || !users) return; - const regexTag = /(<@\d+>)|((?<=^|\s)@[\w\s]+?)(?=\s|$)/g; + const regexTag = /(<@\d+>)|((?<=^|\s)@[\w\s]+?(?::\d+)?)(?=\s|$)/g; const matches = Array.from(value.matchAll(regexTag)); - const freshlyFoundTags: { id: number; name: string }[] = []; + const freshlyFoundTags: { id: number; name: string; personId: number }[] = []; matches.forEach((match) => { const username = match[0]; const user = users.find((u) => `@${u.fullName.replaceAll(/ /g, "")}` === username); - if (user) { + if (user && user.personId != null) { const cleanName = user.fullName.replace(/\s/g, ""); - freshlyFoundTags.push({ id: user.id, name: cleanName }); + freshlyFoundTags.push({ id: user.id, name: cleanName, personId: user.personId }); } }); if (freshlyFoundTags.length > 0) {