overleaf/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-menu.tsx
David 9416e69647 Merge pull request #19985 from overleaf/dp-resolved-threads
Implement redesigned resolved threads popover

GitOrigin-RevId: 4e462eb26a2f2f3194fca89c39d5f9d08ea2e33c
2024-08-20 08:04:35 +00:00

103 lines
2.8 KiB
TypeScript

import React, { FC, useMemo } from 'react'
import { useThreadsContext } from '../context/threads-context'
import { useTranslation } from 'react-i18next'
import { ReviewPanelResolvedThread } from './review-panel-resolved-thread'
import useProjectRanges from '../hooks/use-project-ranges'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import Icon from '@/shared/components/icon'
import { Change, CommentOperation } from '../../../../../types/change'
export const ReviewPanelResolvedThreadsMenu: FC = () => {
const { t } = useTranslation()
const threads = useThreadsContext()
const { docs } = useFileTreeData()
const { projectRanges, loading } = useProjectRanges()
const docNameForThread = useMemo(() => {
const docNameForThread = new Map<string, string>()
for (const [docId, ranges] of projectRanges?.entries() ?? []) {
const docName = docs?.find(doc => doc.doc.id === docId)?.doc.name
if (docName !== undefined) {
for (const comment of ranges.comments) {
const threadId = comment.op.t
docNameForThread.set(threadId, docName)
}
}
}
return docNameForThread
}, [docs, projectRanges])
const allComments = useMemo(() => {
const allComments = new Map<string, Change<CommentOperation>>()
// eslint-disable-next-line no-unused-vars
for (const [_, ranges] of projectRanges?.entries() ?? []) {
for (const comment of ranges.comments) {
allComments.set(comment.op.t, comment)
}
}
return allComments
}, [projectRanges])
const resolvedThreads = useMemo(() => {
if (!threads) {
return []
}
const resolvedThreads = []
for (const [id, thread] of Object.entries(threads)) {
if (thread.resolved) {
resolvedThreads.push({ thread, id })
}
}
return resolvedThreads
}, [threads])
if (loading) {
return (
<div className="review-panel-resolved-comments-loading">
<Icon type="spinner" spin />
</div>
)
}
if (!resolvedThreads.length) {
return (
<div className="review-panel-resolved-comments-empty">
{t('no_resolved_comments')}
</div>
)
}
return (
<>
<div className="review-panel-resolved-comments-header">
<div className="review-panel-resolved-comments-label">
{t('resolved_comments')}
</div>
<div className="review-panel-resolved-comments-count">
{resolvedThreads.length}
</div>
</div>
{resolvedThreads.map(thread => {
const comment = allComments.get(thread.id)
if (!comment) {
return null
}
return (
<ReviewPanelResolvedThread
key={thread.id}
id={thread.id}
comment={comment}
docName={docNameForThread.get(thread.id) ?? t('unknown')}
/>
)
})}
</>
)
}