QueuesTab.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import { useParams } from 'common'
  2. import { RefreshCw, Search, X } from 'lucide-react'
  3. import { useRouter } from 'next/router'
  4. import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
  5. import { useMemo, useState } from 'react'
  6. import DataGrid, { Row } from 'react-data-grid'
  7. import { Button, cn, LoadingLine } from 'ui'
  8. import { Input } from 'ui-patterns/DataInputs/Input'
  9. import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
  10. import { CreateQueueSheet } from './CreateQueueSheet'
  11. import { formatQueueColumns, prepareQueuesForDataGrid } from './Queues.utils'
  12. import AlertError from '@/components/ui/AlertError'
  13. import { useQueuesQuery } from '@/data/database-queues/database-queues-query'
  14. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  15. export const QueuesTab = () => {
  16. const router = useRouter()
  17. const { ref } = useParams()
  18. const { data: project } = useSelectedProjectQuery()
  19. const [searchQuery, setSearchQuery] = useQueryState('search', parseAsString.withDefault(''))
  20. const [search, setSearch] = useState(searchQuery)
  21. const [createQueueSheetShown, setCreateQueueSheetShown] = useQueryState(
  22. 'new',
  23. parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
  24. )
  25. const {
  26. data: queues,
  27. error,
  28. isPending: isLoading,
  29. isError,
  30. isRefetching,
  31. refetch,
  32. } = useQueuesQuery({
  33. projectRef: project?.ref,
  34. connectionString: project?.connectionString,
  35. })
  36. // Filter queues based on search query
  37. const filteredQueues = useMemo(() => {
  38. if (!queues) return []
  39. if (!searchQuery) return queues
  40. return queues.filter((queue) =>
  41. queue.queue_name.toLowerCase().includes(searchQuery.toLowerCase())
  42. )
  43. }, [queues, searchQuery])
  44. // Prepare queues for DataGrid
  45. const queueData = useMemo(() => prepareQueuesForDataGrid(filteredQueues), [filteredQueues])
  46. // Get columns configuration
  47. const columns = useMemo(() => formatQueueColumns(), [])
  48. return (
  49. <>
  50. <div className="h-full w-full space-y-4">
  51. <div className="h-full w-full flex flex-col relative">
  52. <div className="bg-surface-200 py-3 px-10 flex items-center justify-between flex-wrap">
  53. <Input
  54. size="tiny"
  55. className="w-52"
  56. placeholder="Search for a queue"
  57. icon={<Search />}
  58. value={search ?? ''}
  59. onChange={(e) => setSearch(e.target.value)}
  60. onKeyDown={(e) => {
  61. if (e.code === 'Enter' || e.code === 'NumpadEnter') setSearchQuery(search.trim())
  62. }}
  63. actions={[
  64. search && (
  65. <Button
  66. key="clear"
  67. size="tiny"
  68. type="text"
  69. icon={<X />}
  70. onClick={() => {
  71. setSearch('')
  72. setSearchQuery('')
  73. }}
  74. className="p-0 h-5 w-5"
  75. />
  76. ),
  77. ]}
  78. />
  79. <div className="flex items-center gap-x-2">
  80. <Button
  81. type="default"
  82. icon={<RefreshCw />}
  83. loading={isRefetching}
  84. onClick={() => refetch()}
  85. >
  86. Refresh
  87. </Button>
  88. <Button onClick={() => setCreateQueueSheetShown(true)}>Create queue</Button>
  89. </div>
  90. </div>
  91. <LoadingLine loading={isLoading || isRefetching} />
  92. <DataGrid
  93. className="grow border-t-0"
  94. rowHeight={44}
  95. headerRowHeight={36}
  96. columns={columns}
  97. rows={queueData}
  98. rowKeyGetter={(row) => row.id}
  99. rowClass={() => {
  100. return cn(
  101. 'cursor-pointer',
  102. '[&>.rdg-cell]:border-box [&>.rdg-cell]:outline-hidden [&>.rdg-cell]:shadow-none',
  103. '[&>.rdg-cell:first-child>div]:ml-8'
  104. )
  105. }}
  106. renderers={{
  107. renderRow(_, props) {
  108. return (
  109. <Row
  110. key={props.row.queue_name}
  111. {...props}
  112. onClick={() => {
  113. const { queue_name } = props.row
  114. const url = `/project/${ref}/integrations/queues/queues/${queue_name}`
  115. router.push(url)
  116. }}
  117. />
  118. )
  119. },
  120. }}
  121. />
  122. {/* Render 0 rows state outside of the grid */}
  123. {queueData.length === 0 ? (
  124. isLoading ? (
  125. <div className="absolute top-28 px-10 w-full">
  126. <GenericSkeletonLoader />
  127. </div>
  128. ) : isError ? (
  129. <div className="absolute top-28 px-10 flex flex-col items-center justify-center w-full">
  130. <AlertError subject="Failed to retrieve database queues" error={error} />
  131. </div>
  132. ) : (
  133. <div className="absolute top-32 px-6 w-full">
  134. <div className="text-center text-sm flex flex-col gap-y-1">
  135. <p className="text-foreground">
  136. {!!searchQuery ? 'No queues found' : 'No queues created yet'}
  137. </p>
  138. <p className="text-foreground-light">
  139. {!!searchQuery
  140. ? 'There are currently no queues based on the search applied'
  141. : 'There are currently no queues created yet in your project'}
  142. </p>
  143. </div>
  144. </div>
  145. )
  146. ) : null}
  147. <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">
  148. {`Total: ${queueData.length} queues`}
  149. </div>
  150. </div>
  151. </div>
  152. <CreateQueueSheet
  153. visible={createQueueSheetShown}
  154. onClose={() => {
  155. setCreateQueueSheetShown(false)
  156. }}
  157. />
  158. </>
  159. )
  160. }