review-panel-resolved-threads-menu.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import React, { FC, useMemo } from 'react'
  2. import { useThreadsContext } from '../context/threads-context'
  3. import { useTranslation } from 'react-i18next'
  4. import { ReviewPanelResolvedThread } from './review-panel-resolved-thread'
  5. import useProjectRanges from '../hooks/use-project-ranges'
  6. import { useFileTreeData } from '@/shared/context/file-tree-data-context'
  7. import { Change, CommentOperation } from '../../../../../types/change'
  8. import { ThreadId } from '../../../../../types/review-panel/review-panel'
  9. import LoadingSpinner from '@/shared/components/loading-spinner'
  10. import OLBadge from '@/shared/components/ol/ol-badge'
  11. export const ReviewPanelResolvedThreadsMenu: FC = () => {
  12. const { t } = useTranslation()
  13. const threads = useThreadsContext()
  14. const { docs } = useFileTreeData()
  15. const { projectRanges, loading } = useProjectRanges()
  16. const docNameForThread = useMemo(() => {
  17. const docNameForThread = new Map<string, string>()
  18. for (const [docId, ranges] of projectRanges?.entries() ?? []) {
  19. const docName = docs?.find(doc => doc.doc.id === docId)?.doc.name
  20. if (docName !== undefined) {
  21. for (const comment of ranges.comments) {
  22. const threadId = comment.op.t
  23. docNameForThread.set(threadId, docName)
  24. }
  25. }
  26. }
  27. return docNameForThread
  28. }, [docs, projectRanges])
  29. const allComments = useMemo(() => {
  30. const allComments = new Map<string, Change<CommentOperation>>()
  31. // eslint-disable-next-line no-unused-vars
  32. for (const [_, ranges] of projectRanges?.entries() ?? []) {
  33. for (const comment of ranges.comments) {
  34. allComments.set(comment.op.t, comment)
  35. }
  36. }
  37. return allComments
  38. }, [projectRanges])
  39. const resolvedThreads = useMemo(() => {
  40. if (!threads) {
  41. return []
  42. }
  43. const allResolvedThreads = []
  44. for (const [id, thread] of Object.entries(threads)) {
  45. if (thread.resolved) {
  46. allResolvedThreads.push({ thread, id })
  47. }
  48. }
  49. allResolvedThreads.sort((a, b) => {
  50. return Date.parse(b.thread.resolved_at) - Date.parse(a.thread.resolved_at)
  51. })
  52. return allResolvedThreads.filter(thread => allComments.has(thread.id))
  53. }, [threads, allComments])
  54. if (loading) {
  55. return <LoadingSpinner className="ms-auto me-auto" />
  56. }
  57. if (!resolvedThreads.length) {
  58. return (
  59. <div className="review-panel-resolved-comments-empty">
  60. {t('no_resolved_comments')}
  61. </div>
  62. )
  63. }
  64. return (
  65. <>
  66. <div className="review-panel-resolved-comments-header">
  67. <div className="review-panel-resolved-comments-label">
  68. {t('resolved_comments')}
  69. </div>
  70. <OLBadge
  71. bg="light"
  72. text="dark"
  73. className="review-panel-resolved-comments-count"
  74. >
  75. {resolvedThreads.length}
  76. </OLBadge>
  77. </div>
  78. {resolvedThreads.map(thread => {
  79. const comment = allComments.get(thread.id)
  80. if (!comment) {
  81. return null
  82. }
  83. return (
  84. <ReviewPanelResolvedThread
  85. key={thread.id}
  86. id={thread.id as ThreadId}
  87. comment={comment}
  88. docName={docNameForThread.get(thread.id) ?? t('unknown')}
  89. />
  90. )
  91. })}
  92. </>
  93. )
  94. }