UsersFooter.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { THRESHOLD_COUNT, type OptimizedSearchColumns } from '@supabase/pg-meta'
  2. import { keepPreviousData } from '@tanstack/react-query'
  3. import { useParams } from 'common'
  4. import { HelpCircle, Loader2 } from 'lucide-react'
  5. import { useEffect, useState } from 'react'
  6. import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  7. import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
  8. import type { Filter, SpecificFilterColumn } from './Users.constants'
  9. import { formatEstimatedCount } from '@/components/grid/components/footer/pagination/Pagination.utils'
  10. import { useUsersCountQuery } from '@/data/auth/users-count-query'
  11. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  12. interface UsersFooterProps {
  13. filter: Filter
  14. filterKeywords: string
  15. selectedProviders: string[]
  16. specificFilterColumn: SpecificFilterColumn
  17. }
  18. export const UsersFooter = ({
  19. filter,
  20. filterKeywords,
  21. selectedProviders,
  22. specificFilterColumn,
  23. }: UsersFooterProps) => {
  24. const { ref: projectRef } = useParams()
  25. const { data: project } = useSelectedProjectQuery()
  26. const [forceExactCount, setForceExactCount] = useState(false)
  27. const [showFetchExactCountModal, setShowFetchExactCountModal] = useState(false)
  28. const {
  29. data: countData,
  30. isPending: isLoadingCount,
  31. isFetching: isFetchingCount,
  32. isSuccess: isSuccessCount,
  33. } = useUsersCountQuery(
  34. {
  35. projectRef,
  36. connectionString: project?.connectionString,
  37. keywords: filterKeywords,
  38. filter: filter === 'all' ? undefined : filter,
  39. providers: selectedProviders,
  40. forceExactCount,
  41. // Use optimized search when filtering by specific column
  42. ...(specificFilterColumn !== 'freeform'
  43. ? { column: specificFilterColumn as OptimizedSearchColumns }
  44. : { column: undefined }),
  45. },
  46. { placeholderData: keepPreviousData }
  47. )
  48. const totalUsers = countData?.count ?? 0
  49. useEffect(() => {
  50. if (isSuccessCount && specificFilterColumn === 'freeform') {
  51. setForceExactCount(countData.is_estimate && countData.count <= THRESHOLD_COUNT)
  52. }
  53. // eslint-disable-next-line react-hooks/exhaustive-deps
  54. }, [isSuccessCount, specificFilterColumn])
  55. return (
  56. <>
  57. <div className="flex justify-between min-h-9 h-9 overflow-hidden items-center px-6 w-full border-t text-xs text-foreground-light">
  58. <div className="flex items-center gap-x-2">
  59. {isLoadingCount || isFetchingCount || countData === undefined ? (
  60. <span className="flex items-center gap-2">
  61. <Loader2 size={14} className="animate-spin" /> Loading...
  62. </span>
  63. ) : (
  64. <>
  65. <span>
  66. Total:{' '}
  67. {countData?.is_estimate
  68. ? formatEstimatedCount(totalUsers)
  69. : totalUsers.toLocaleString()}{' '}
  70. user{totalUsers !== 1 ? 's' : ''}
  71. {countData?.is_estimate && ' (estimated)'}
  72. </span>
  73. {countData?.is_estimate && (
  74. <Tooltip>
  75. <TooltipTrigger asChild>
  76. <Button
  77. size="tiny"
  78. type="text"
  79. className="px-1.5"
  80. icon={<HelpCircle />}
  81. onClick={() => {
  82. if (specificFilterColumn === 'freeform') {
  83. setShowFetchExactCountModal(true)
  84. }
  85. }}
  86. />
  87. </TooltipTrigger>
  88. <TooltipContent side="top" className="w-80">
  89. {specificFilterColumn === 'freeform' ? (
  90. <>
  91. This is an estimated value as your project has more than{' '}
  92. {THRESHOLD_COUNT.toLocaleString()} users.
  93. <br />
  94. <span className="text-brand">Click to retrieve the exact count.</span>{' '}
  95. </>
  96. ) : (
  97. <>
  98. <p className="mb-1">
  99. This is an estimated value which may not be accurate.
  100. </p>
  101. <p>
  102. If you'd like to retrieve the exact count, change the search to{' '}
  103. <span className="text-warning">all columns</span> from the header.
  104. </p>{' '}
  105. </>
  106. )}
  107. </TooltipContent>
  108. </Tooltip>
  109. )}
  110. </>
  111. )}
  112. </div>
  113. </div>
  114. <ConfirmationModal
  115. variant="warning"
  116. visible={showFetchExactCountModal}
  117. title="Fetch exact user count"
  118. confirmLabel="Fetch exact count"
  119. onCancel={() => setShowFetchExactCountModal(false)}
  120. onConfirm={() => {
  121. setForceExactCount(true)
  122. setShowFetchExactCountModal(false)
  123. }}
  124. >
  125. <p className="text-sm text-foreground-light">
  126. Your project has more than {THRESHOLD_COUNT.toLocaleString()} users, and fetching the
  127. exact count may cause performance issues on your database.
  128. </p>
  129. </ConfirmationModal>
  130. </>
  131. )
  132. }