list.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /* eslint-disable no-mixed-operators */
  2. 'use client'
  3. import type { FC, SVGProps } from 'react'
  4. import React, { useCallback, useEffect, useState } from 'react'
  5. import { useBoolean, useDebounceFn } from 'ahooks'
  6. import { ArrowDownIcon, TrashIcon } from '@heroicons/react/24/outline'
  7. import { pick } from 'lodash-es'
  8. import {
  9. RiMoreFill,
  10. } from '@remixicon/react'
  11. import { useContext } from 'use-context-selector'
  12. import { useRouter } from 'next/navigation'
  13. import { useTranslation } from 'react-i18next'
  14. import dayjs from 'dayjs'
  15. import { Edit03 } from '../../base/icons/src/vender/solid/general'
  16. import { Globe01 } from '../../base/icons/src/vender/line/mapsAndTravel'
  17. import s from './style.module.css'
  18. import RenameModal from './rename-modal'
  19. import cn from '@/utils/classnames'
  20. import Switch from '@/app/components/base/switch'
  21. import Divider from '@/app/components/base/divider'
  22. import Popover from '@/app/components/base/popover'
  23. import Confirm from '@/app/components/base/confirm'
  24. import Tooltip from '@/app/components/base/tooltip'
  25. import { ToastContext } from '@/app/components/base/toast'
  26. import type { IndicatorProps } from '@/app/components/header/indicator'
  27. import Indicator from '@/app/components/header/indicator'
  28. import { asyncRunSafe } from '@/utils'
  29. import { formatNumber } from '@/utils/format'
  30. import { archiveDocument, deleteDocument, disableDocument, enableDocument, syncDocument, syncWebsite, unArchiveDocument } from '@/service/datasets'
  31. import NotionIcon from '@/app/components/base/notion-icon'
  32. import ProgressBar from '@/app/components/base/progress-bar'
  33. import { DataSourceType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets'
  34. import type { CommonResponse } from '@/models/common'
  35. import useTimestamp from '@/hooks/use-timestamp'
  36. export const SettingsIcon = ({ className }: SVGProps<SVGElement>) => {
  37. return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
  38. <path d="M2 5.33325L10 5.33325M10 5.33325C10 6.43782 10.8954 7.33325 12 7.33325C13.1046 7.33325 14 6.43782 14 5.33325C14 4.22868 13.1046 3.33325 12 3.33325C10.8954 3.33325 10 4.22868 10 5.33325ZM6 10.6666L14 10.6666M6 10.6666C6 11.7712 5.10457 12.6666 4 12.6666C2.89543 12.6666 2 11.7712 2 10.6666C2 9.56202 2.89543 8.66659 4 8.66659C5.10457 8.66659 6 9.56202 6 10.6666Z" stroke="#667085" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
  39. </svg>
  40. }
  41. export const SyncIcon = () => {
  42. return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
  43. <path d="M5.69773 13.1783C7.29715 13.8879 9.20212 13.8494 10.8334 12.9075C13.5438 11.3427 14.4724 7.87704 12.9076 5.16672L12.7409 4.87804M3.09233 10.8335C1.52752 8.12314 2.45615 4.65746 5.16647 3.09265C6.7978 2.15081 8.70277 2.11227 10.3022 2.82185M1.66226 10.8892L3.48363 11.3773L3.97166 9.5559M12.0284 6.44393L12.5164 4.62256L14.3378 5.1106" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
  44. </svg>
  45. }
  46. export const FilePlusIcon = ({ className }: SVGProps<SVGElement>) => {
  47. return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
  48. <path d="M13.3332 6.99992V4.53325C13.3332 3.41315 13.3332 2.85309 13.1152 2.42527C12.9234 2.04895 12.6175 1.74299 12.2412 1.55124C11.8133 1.33325 11.2533 1.33325 10.1332 1.33325H5.8665C4.7464 1.33325 4.18635 1.33325 3.75852 1.55124C3.3822 1.74299 3.07624 2.04895 2.88449 2.42527C2.6665 2.85309 2.6665 3.41315 2.6665 4.53325V11.4666C2.6665 12.5867 2.6665 13.1467 2.88449 13.5746C3.07624 13.9509 3.3822 14.2569 3.75852 14.4486C4.18635 14.6666 4.7464 14.6666 5.8665 14.6666H7.99984M9.33317 7.33325H5.33317M6.6665 9.99992H5.33317M10.6665 4.66659H5.33317M11.9998 13.9999V9.99992M9.99984 11.9999H13.9998" stroke="#667085" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
  49. </svg>
  50. }
  51. export const ArchiveIcon = ({ className }: SVGProps<SVGElement>) => {
  52. return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
  53. <path d="M2.66683 5.33106C2.55749 5.32824 2.47809 5.32191 2.40671 5.30771C1.87779 5.2025 1.46432 4.78904 1.35912 4.26012C1.3335 4.13132 1.3335 3.97644 1.3335 3.66667C1.3335 3.3569 1.3335 3.20201 1.35912 3.07321C1.46432 2.54429 1.87779 2.13083 2.40671 2.02562C2.53551 2 2.69039 2 3.00016 2H13.0002C13.3099 2 13.4648 2 13.5936 2.02562C14.1225 2.13083 14.536 2.54429 14.6412 3.07321C14.6668 3.20201 14.6668 3.3569 14.6668 3.66667C14.6668 3.97644 14.6668 4.13132 14.6412 4.26012C14.536 4.78904 14.1225 5.2025 13.5936 5.30771C13.5222 5.32191 13.4428 5.32824 13.3335 5.33106M6.66683 8.66667H9.3335M2.66683 5.33333H13.3335V10.8C13.3335 11.9201 13.3335 12.4802 13.1155 12.908C12.9238 13.2843 12.6178 13.5903 12.2415 13.782C11.8137 14 11.2536 14 10.1335 14H5.86683C4.74672 14 4.18667 14 3.75885 13.782C3.38252 13.5903 3.07656 13.2843 2.88482 12.908C2.66683 12.4802 2.66683 11.9201 2.66683 10.8V5.33333Z" stroke="#667085" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
  54. </svg>
  55. }
  56. export const useIndexStatus = () => {
  57. const { t } = useTranslation()
  58. return {
  59. queuing: { color: 'orange', text: t('datasetDocuments.list.status.queuing') }, // waiting
  60. indexing: { color: 'blue', text: t('datasetDocuments.list.status.indexing') }, // indexing splitting parsing cleaning
  61. paused: { color: 'orange', text: t('datasetDocuments.list.status.paused') }, // paused
  62. error: { color: 'red', text: t('datasetDocuments.list.status.error') }, // error
  63. available: { color: 'green', text: t('datasetDocuments.list.status.available') }, // completed,archived = false,enabled = true
  64. enabled: { color: 'green', text: t('datasetDocuments.list.status.enabled') }, // completed,archived = false,enabled = true
  65. disabled: { color: 'gray', text: t('datasetDocuments.list.status.disabled') }, // completed,archived = false,enabled = false
  66. archived: { color: 'gray', text: t('datasetDocuments.list.status.archived') }, // completed,archived = true
  67. }
  68. }
  69. // status item for list
  70. export const StatusItem: FC<{
  71. status: DocumentDisplayStatus
  72. reverse?: boolean
  73. scene?: 'list' | 'detail'
  74. textCls?: string
  75. errorMessage?: string
  76. }> = ({ status, reverse = false, scene = 'list', textCls = '', errorMessage }) => {
  77. const DOC_INDEX_STATUS_MAP = useIndexStatus()
  78. const localStatus = status.toLowerCase() as keyof typeof DOC_INDEX_STATUS_MAP
  79. return <div className={
  80. cn('flex items-center',
  81. reverse ? 'flex-row-reverse' : '',
  82. scene === 'detail' ? s.statusItemDetail : '')
  83. }>
  84. <Indicator color={DOC_INDEX_STATUS_MAP[localStatus]?.color as IndicatorProps['color']} className={reverse ? 'ml-2' : 'mr-2'} />
  85. <span className={cn('text-gray-700 text-sm', textCls)}>{DOC_INDEX_STATUS_MAP[localStatus]?.text}</span>
  86. {
  87. errorMessage && (
  88. <Tooltip
  89. popupContent={
  90. <div className='max-w-[260px] break-all'>{errorMessage}</div>
  91. }
  92. triggerClassName='ml-1 w-4 h-4'
  93. />
  94. )
  95. }
  96. </div>
  97. }
  98. type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive'
  99. // operation action for list and detail
  100. export const OperationAction: FC<{
  101. embeddingAvailable: boolean
  102. detail: {
  103. name: string
  104. enabled: boolean
  105. archived: boolean
  106. id: string
  107. data_source_type: string
  108. doc_form: string
  109. }
  110. datasetId: string
  111. onUpdate: (operationName?: string) => void
  112. scene?: 'list' | 'detail'
  113. className?: string
  114. }> = ({ embeddingAvailable, datasetId, detail, onUpdate, scene = 'list', className = '' }) => {
  115. const { id, enabled = false, archived = false, data_source_type } = detail || {}
  116. const [showModal, setShowModal] = useState(false)
  117. const [deleting, setDeleting] = useState(false)
  118. const { notify } = useContext(ToastContext)
  119. const { t } = useTranslation()
  120. const router = useRouter()
  121. const isListScene = scene === 'list'
  122. const onOperate = async (operationName: OperationName) => {
  123. let opApi = deleteDocument
  124. switch (operationName) {
  125. case 'archive':
  126. opApi = archiveDocument
  127. break
  128. case 'un_archive':
  129. opApi = unArchiveDocument
  130. break
  131. case 'enable':
  132. opApi = enableDocument
  133. break
  134. case 'disable':
  135. opApi = disableDocument
  136. break
  137. case 'sync':
  138. if (data_source_type === 'notion_import')
  139. opApi = syncDocument
  140. else
  141. opApi = syncWebsite
  142. break
  143. default:
  144. opApi = deleteDocument
  145. setDeleting(true)
  146. break
  147. }
  148. const [e] = await asyncRunSafe<CommonResponse>(opApi({ datasetId, documentId: id }) as Promise<CommonResponse>)
  149. if (!e)
  150. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  151. else
  152. notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  153. if (operationName === 'delete')
  154. setDeleting(false)
  155. onUpdate(operationName)
  156. }
  157. const { run: handleSwitch } = useDebounceFn((operationName: OperationName) => {
  158. if (operationName === 'enable' && enabled)
  159. return
  160. if (operationName === 'disable' && !enabled)
  161. return
  162. onOperate(operationName)
  163. }, { wait: 500 })
  164. const [currDocument, setCurrDocument] = useState<{
  165. id: string
  166. name: string
  167. } | null>(null)
  168. const [isShowRenameModal, {
  169. setTrue: setShowRenameModalTrue,
  170. setFalse: setShowRenameModalFalse,
  171. }] = useBoolean(false)
  172. const handleShowRenameModal = useCallback((doc: {
  173. id: string
  174. name: string
  175. }) => {
  176. setCurrDocument(doc)
  177. setShowRenameModalTrue()
  178. }, [setShowRenameModalTrue])
  179. const handleRenamed = useCallback(() => {
  180. onUpdate()
  181. }, [onUpdate])
  182. return <div className='flex items-center' onClick={e => e.stopPropagation()}>
  183. {isListScene && !embeddingAvailable && (
  184. <Switch defaultValue={false} onChange={() => { }} disabled={true} size='md' />
  185. )}
  186. {isListScene && embeddingAvailable && (
  187. <>
  188. {archived
  189. ? <Tooltip
  190. popupContent={t('datasetDocuments.list.action.enableWarning')}
  191. popupClassName='!font-semibold'
  192. needsDelay
  193. >
  194. <div>
  195. <Switch defaultValue={false} onChange={() => { }} disabled={true} size='md' />
  196. </div>
  197. </Tooltip>
  198. : <Switch defaultValue={enabled} onChange={v => handleSwitch(v ? 'enable' : 'disable')} size='md' />
  199. }
  200. <Divider className='!ml-4 !mr-2 !h-3' type='vertical' />
  201. </>
  202. )}
  203. {embeddingAvailable && (
  204. <Popover
  205. htmlContent={
  206. <div className='w-full py-1'>
  207. {!isListScene && <>
  208. <div className='flex justify-between items-center mx-4 pt-2'>
  209. <span className={cn(s.actionName, 'font-medium')}>
  210. {!archived && enabled ? t('datasetDocuments.list.index.enable') : t('datasetDocuments.list.index.disable')}
  211. </span>
  212. <Tooltip
  213. popupContent={t('datasetDocuments.list.action.enableWarning')}
  214. popupClassName='!font-semibold'
  215. needsDelay
  216. disabled={!archived}
  217. >
  218. <div>
  219. <Switch
  220. defaultValue={archived ? false : enabled}
  221. onChange={v => !archived && handleSwitch(v ? 'enable' : 'disable')}
  222. disabled={archived}
  223. size='md'
  224. />
  225. </div>
  226. </Tooltip>
  227. </div>
  228. <div className='mx-4 pb-1 pt-0.5 text-xs text-gray-500'>
  229. {!archived && enabled ? t('datasetDocuments.list.index.enableTip') : t('datasetDocuments.list.index.disableTip')}
  230. </div>
  231. <Divider />
  232. </>}
  233. {!archived && (
  234. <>
  235. <div className={s.actionItem} onClick={() => {
  236. handleShowRenameModal({
  237. id: detail.id,
  238. name: detail.name,
  239. })
  240. }}>
  241. <Edit03 className='w-4 h-4 text-gray-500' />
  242. <span className={s.actionName}>{t('datasetDocuments.list.table.rename')}</span>
  243. </div>
  244. <div className={s.actionItem} onClick={() => router.push(`/datasets/${datasetId}/documents/${detail.id}/settings`)}>
  245. <SettingsIcon />
  246. <span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
  247. </div>
  248. {['notion_import', DataSourceType.WEB].includes(data_source_type) && (
  249. <div className={s.actionItem} onClick={() => onOperate('sync')}>
  250. <SyncIcon />
  251. <span className={s.actionName}>{t('datasetDocuments.list.action.sync')}</span>
  252. </div>
  253. )}
  254. <Divider className='my-1' />
  255. </>
  256. )}
  257. {!archived && <div className={s.actionItem} onClick={() => onOperate('archive')}>
  258. <ArchiveIcon />
  259. <span className={s.actionName}>{t('datasetDocuments.list.action.archive')}</span>
  260. </div>}
  261. {archived && (
  262. <div className={s.actionItem} onClick={() => onOperate('un_archive')}>
  263. <ArchiveIcon />
  264. <span className={s.actionName}>{t('datasetDocuments.list.action.unarchive')}</span>
  265. </div>
  266. )}
  267. <div className={cn(s.actionItem, s.deleteActionItem, 'group')} onClick={() => setShowModal(true)}>
  268. <TrashIcon className={'w-4 h-4 stroke-current text-gray-500 stroke-2 group-hover:text-red-500'} />
  269. <span className={cn(s.actionName, 'group-hover:text-red-500')}>{t('datasetDocuments.list.action.delete')}</span>
  270. </div>
  271. </div>
  272. }
  273. trigger='click'
  274. position='br'
  275. btnElement={
  276. <div className={cn(s.commonIcon)}>
  277. <RiMoreFill className='w-4 h-4 text-gray-700' />
  278. </div>
  279. }
  280. btnClassName={open => cn(isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail, open ? '!bg-gray-100 !shadow-none' : '!bg-transparent')}
  281. className={`flex justify-end !w-[200px] h-fit !z-20 ${className}`}
  282. />
  283. )}
  284. {showModal
  285. && <Confirm
  286. isShow={showModal}
  287. isLoading={deleting}
  288. isDisabled={deleting}
  289. title={t('datasetDocuments.list.delete.title')}
  290. content={t('datasetDocuments.list.delete.content')}
  291. confirmText={t('common.operation.sure')}
  292. onConfirm={() => onOperate('delete')}
  293. onCancel={() => setShowModal(false)}
  294. />
  295. }
  296. {isShowRenameModal && currDocument && (
  297. <RenameModal
  298. datasetId={datasetId}
  299. documentId={currDocument.id}
  300. name={currDocument.name}
  301. onClose={setShowRenameModalFalse}
  302. onSaved={handleRenamed}
  303. />
  304. )}
  305. </div>
  306. }
  307. export const renderTdValue = (value: string | number | null, isEmptyStyle = false) => {
  308. return (
  309. <div className={cn(isEmptyStyle ? 'text-gray-400' : 'text-gray-700', s.tdValue)}>
  310. {value ?? '-'}
  311. </div>
  312. )
  313. }
  314. const renderCount = (count: number | undefined) => {
  315. if (!count)
  316. return renderTdValue(0, true)
  317. if (count < 1000)
  318. return count
  319. return `${formatNumber((count / 1000).toFixed(1))}k`
  320. }
  321. type LocalDoc = SimpleDocumentDetail & { percent?: number }
  322. type IDocumentListProps = {
  323. embeddingAvailable: boolean
  324. documents: LocalDoc[]
  325. datasetId: string
  326. onUpdate: () => void
  327. }
  328. /**
  329. * Document list component including basic information
  330. */
  331. const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents = [], datasetId, onUpdate }) => {
  332. const { t } = useTranslation()
  333. const { formatTime } = useTimestamp()
  334. const router = useRouter()
  335. const [localDocs, setLocalDocs] = useState<LocalDoc[]>(documents)
  336. const [enableSort, setEnableSort] = useState(false)
  337. useEffect(() => {
  338. setLocalDocs(documents)
  339. }, [documents])
  340. const onClickSort = () => {
  341. setEnableSort(!enableSort)
  342. if (!enableSort) {
  343. const sortedDocs = [...localDocs].sort((a, b) => dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? -1 : 1)
  344. setLocalDocs(sortedDocs)
  345. }
  346. else {
  347. setLocalDocs(documents)
  348. }
  349. }
  350. const [currDocument, setCurrDocument] = useState<LocalDoc | null>(null)
  351. const [isShowRenameModal, {
  352. setTrue: setShowRenameModalTrue,
  353. setFalse: setShowRenameModalFalse,
  354. }] = useBoolean(false)
  355. const handleShowRenameModal = useCallback((doc: LocalDoc) => {
  356. setCurrDocument(doc)
  357. setShowRenameModalTrue()
  358. }, [setShowRenameModalTrue])
  359. const handleRenamed = useCallback(() => {
  360. onUpdate()
  361. }, [onUpdate])
  362. return (
  363. <div className='w-full h-full overflow-x-auto'>
  364. <table className={`min-w-[700px] max-w-full w-full border-collapse border-0 text-sm mt-3 ${s.documentTable}`}>
  365. <thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-medium text-xs uppercase">
  366. <tr>
  367. <td className='w-12'>#</td>
  368. <td>
  369. <div className='flex'>
  370. {t('datasetDocuments.list.table.header.fileName')}
  371. </div>
  372. </td>
  373. <td className='w-24'>{t('datasetDocuments.list.table.header.words')}</td>
  374. <td className='w-44'>{t('datasetDocuments.list.table.header.hitCount')}</td>
  375. <td className='w-44'>
  376. <div className='flex justify-between items-center'>
  377. {t('datasetDocuments.list.table.header.uploadTime')}
  378. <ArrowDownIcon className={cn('h-3 w-3 stroke-current stroke-2 cursor-pointer', enableSort ? 'text-gray-500' : 'text-gray-300')} onClick={onClickSort} />
  379. </div>
  380. </td>
  381. <td className='w-40'>{t('datasetDocuments.list.table.header.status')}</td>
  382. <td className='w-20'>{t('datasetDocuments.list.table.header.action')}</td>
  383. </tr>
  384. </thead>
  385. <tbody className="text-gray-700">
  386. {localDocs.map((doc) => {
  387. const isFile = doc.data_source_type === DataSourceType.FILE
  388. const fileType = isFile ? doc.data_source_detail_dict?.upload_file?.extension : ''
  389. return <tr
  390. key={doc.id}
  391. className={'border-b border-gray-200 h-8 hover:bg-gray-50 cursor-pointer'}
  392. onClick={() => {
  393. router.push(`/datasets/${datasetId}/documents/${doc.id}`)
  394. }}>
  395. <td className='text-left align-middle text-gray-500 text-xs'>{doc.position}</td>
  396. <td>
  397. <div className='group flex items-center justify-between'>
  398. <span className={s.tdValue}>
  399. {doc?.data_source_type === DataSourceType.NOTION && <NotionIcon className='inline-flex -mt-[3px] mr-1.5 align-middle' type='page' src={doc.data_source_info.notion_page_icon} />
  400. }
  401. {doc?.data_source_type === DataSourceType.FILE && <div className={cn(s[`${doc?.data_source_info?.upload_file?.extension ?? fileType}Icon`], s.commonIcon, 'mr-1.5')}></div>}
  402. {doc?.data_source_type === DataSourceType.WEB && <Globe01 className='inline-flex -mt-[3px] mr-1.5 align-middle' />
  403. }
  404. {
  405. doc.name
  406. }
  407. </span>
  408. <div className='group-hover:flex hidden'>
  409. <Tooltip
  410. popupContent={t('datasetDocuments.list.table.rename')}
  411. >
  412. <div
  413. className='p-1 rounded-md cursor-pointer hover:bg-black/5'
  414. onClick={(e) => {
  415. e.stopPropagation()
  416. handleShowRenameModal(doc)
  417. }}
  418. >
  419. <Edit03 className='w-4 h-4 text-gray-500' />
  420. </div>
  421. </Tooltip>
  422. </div>
  423. </div>
  424. </td>
  425. <td>{renderCount(doc.word_count)}</td>
  426. <td>{renderCount(doc.hit_count)}</td>
  427. <td className='text-gray-500 text-[13px]'>
  428. {formatTime(doc.created_at, t('datasetHitTesting.dateTimeFormat') as string)}
  429. </td>
  430. <td>
  431. {
  432. (['indexing', 'splitting', 'parsing', 'cleaning'].includes(doc.indexing_status) && doc?.data_source_type === DataSourceType.NOTION)
  433. ? <ProgressBar percent={doc.percent || 0} />
  434. : <StatusItem status={doc.display_status} />
  435. }
  436. </td>
  437. <td>
  438. <OperationAction
  439. embeddingAvailable={embeddingAvailable}
  440. datasetId={datasetId}
  441. detail={pick(doc, ['name', 'enabled', 'archived', 'id', 'data_source_type', 'doc_form'])}
  442. onUpdate={onUpdate}
  443. />
  444. </td>
  445. </tr>
  446. })}
  447. </tbody>
  448. </table>
  449. {isShowRenameModal && currDocument && (
  450. <RenameModal
  451. datasetId={datasetId}
  452. documentId={currDocument.id}
  453. name={currDocument.name}
  454. onClose={setShowRenameModalFalse}
  455. onSaved={handleRenamed}
  456. />
  457. )}
  458. </div>
  459. )
  460. }
  461. export default DocumentList