Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

files-table.tsx 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. 'use client';
  2. import {
  3. ColumnDef,
  4. ColumnFiltersState,
  5. OnChangeFn,
  6. RowSelectionState,
  7. SortingState,
  8. VisibilityState,
  9. flexRender,
  10. getCoreRowModel,
  11. getFilteredRowModel,
  12. getSortedRowModel,
  13. useReactTable,
  14. } from '@tanstack/react-table';
  15. import { ArrowUpDown } from 'lucide-react';
  16. import * as React from 'react';
  17. import { RenameDialog } from '@/components/rename-dialog';
  18. import SvgIcon from '@/components/svg-icon';
  19. import { TableEmpty, TableSkeleton } from '@/components/table-skeleton';
  20. import { Badge } from '@/components/ui/badge';
  21. import { Button } from '@/components/ui/button';
  22. import { Checkbox } from '@/components/ui/checkbox';
  23. import {
  24. Table,
  25. TableBody,
  26. TableCell,
  27. TableHead,
  28. TableHeader,
  29. TableRow,
  30. } from '@/components/ui/table';
  31. import {
  32. Tooltip,
  33. TooltipContent,
  34. TooltipTrigger,
  35. } from '@/components/ui/tooltip';
  36. import { useFetchFileList } from '@/hooks/use-file-request';
  37. import { IFile } from '@/interfaces/database/file-manager';
  38. import { cn } from '@/lib/utils';
  39. import { formatFileSize } from '@/utils/common-util';
  40. import { formatDate } from '@/utils/date';
  41. import { getExtension } from '@/utils/document-util';
  42. import { useMemo } from 'react';
  43. import { useTranslation } from 'react-i18next';
  44. import { ActionCell } from './action-cell';
  45. import { useHandleConnectToKnowledge, useRenameCurrentFile } from './hooks';
  46. import { LinkToDatasetDialog } from './link-to-dataset-dialog';
  47. import { UseMoveDocumentShowType } from './use-move-file';
  48. import { useNavigateToOtherFolder } from './use-navigate-to-folder';
  49. type FilesTableProps = Pick<
  50. ReturnType<typeof useFetchFileList>,
  51. 'files' | 'loading' | 'pagination' | 'setPagination' | 'total'
  52. > & {
  53. rowSelection: RowSelectionState;
  54. setRowSelection: OnChangeFn<RowSelectionState>;
  55. } & UseMoveDocumentShowType;
  56. export function FilesTable({
  57. files,
  58. total,
  59. pagination,
  60. setPagination,
  61. loading,
  62. rowSelection,
  63. setRowSelection,
  64. showMoveFileModal,
  65. }: FilesTableProps) {
  66. const [sorting, setSorting] = React.useState<SortingState>([]);
  67. const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
  68. [],
  69. );
  70. const [columnVisibility, setColumnVisibility] =
  71. React.useState<VisibilityState>({});
  72. const { t } = useTranslation('translation', {
  73. keyPrefix: 'fileManager',
  74. });
  75. const navigateToOtherFolder = useNavigateToOtherFolder();
  76. const {
  77. connectToKnowledgeVisible,
  78. hideConnectToKnowledgeModal,
  79. showConnectToKnowledgeModal,
  80. initialConnectedIds,
  81. onConnectToKnowledgeOk,
  82. connectToKnowledgeLoading,
  83. } = useHandleConnectToKnowledge();
  84. const {
  85. fileRenameVisible,
  86. showFileRenameModal,
  87. hideFileRenameModal,
  88. onFileRenameOk,
  89. initialFileName,
  90. fileRenameLoading,
  91. } = useRenameCurrentFile();
  92. const columns: ColumnDef<IFile>[] = [
  93. {
  94. id: 'select',
  95. header: ({ table }) => (
  96. <Checkbox
  97. checked={
  98. table.getIsAllPageRowsSelected() ||
  99. (table.getIsSomePageRowsSelected() && 'indeterminate')
  100. }
  101. onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
  102. aria-label="Select all"
  103. />
  104. ),
  105. cell: ({ row }) => (
  106. <Checkbox
  107. checked={row.getIsSelected()}
  108. onCheckedChange={(value) => row.toggleSelected(!!value)}
  109. aria-label="Select row"
  110. />
  111. ),
  112. enableSorting: false,
  113. enableHiding: false,
  114. },
  115. {
  116. accessorKey: 'name',
  117. header: ({ column }) => {
  118. return (
  119. <Button
  120. variant="ghost"
  121. onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
  122. >
  123. {t('name')}
  124. <ArrowUpDown />
  125. </Button>
  126. );
  127. },
  128. meta: { cellClassName: 'max-w-[20vw]' },
  129. cell: ({ row }) => {
  130. const name: string = row.getValue('name');
  131. const type = row.original.type;
  132. const id = row.original.id;
  133. const isFolder = type === 'folder';
  134. const handleNameClick = () => {
  135. if (isFolder) {
  136. navigateToOtherFolder(id);
  137. }
  138. };
  139. return (
  140. <Tooltip>
  141. <TooltipTrigger asChild>
  142. <div className="flex gap-2">
  143. <SvgIcon
  144. name={`file-icon/${isFolder ? 'folder' : getExtension(name)}`}
  145. width={24}
  146. ></SvgIcon>
  147. <span
  148. className={cn('truncate', { ['cursor-pointer']: isFolder })}
  149. onClick={handleNameClick}
  150. >
  151. {name}
  152. </span>
  153. </div>
  154. </TooltipTrigger>
  155. <TooltipContent>
  156. <p>{name}</p>
  157. </TooltipContent>
  158. </Tooltip>
  159. );
  160. },
  161. },
  162. {
  163. accessorKey: 'create_time',
  164. header: ({ column }) => {
  165. return (
  166. <Button
  167. variant="ghost"
  168. onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
  169. >
  170. {t('uploadDate')}
  171. <ArrowUpDown />
  172. </Button>
  173. );
  174. },
  175. cell: ({ row }) => (
  176. <div className="lowercase">
  177. {formatDate(row.getValue('create_time'))}
  178. </div>
  179. ),
  180. },
  181. {
  182. accessorKey: 'size',
  183. header: ({ column }) => {
  184. return (
  185. <Button
  186. variant="ghost"
  187. onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
  188. >
  189. {t('size')}
  190. <ArrowUpDown />
  191. </Button>
  192. );
  193. },
  194. cell: ({ row }) => (
  195. <div className="capitalize">{formatFileSize(row.getValue('size'))}</div>
  196. ),
  197. },
  198. {
  199. accessorKey: 'kbs_info',
  200. header: t('knowledgeBase'),
  201. cell: ({ row }) => {
  202. const value = row.getValue('kbs_info');
  203. return Array.isArray(value) ? (
  204. <section className="flex gap-2 items-center">
  205. {value?.slice(0, 2).map((x) => (
  206. <Badge key={x.kb_id} className="" variant={'tertiary'}>
  207. {x.kb_name}
  208. </Badge>
  209. ))}
  210. {value.length > 2 && (
  211. <Button variant={'icon'} size={'auto'}>
  212. +{value.length - 2}
  213. </Button>
  214. )}
  215. </section>
  216. ) : (
  217. ''
  218. );
  219. },
  220. },
  221. {
  222. id: 'actions',
  223. header: t('action'),
  224. enableHiding: false,
  225. cell: ({ row }) => {
  226. return (
  227. <ActionCell
  228. row={row}
  229. showConnectToKnowledgeModal={showConnectToKnowledgeModal}
  230. showFileRenameModal={showFileRenameModal}
  231. showMoveFileModal={showMoveFileModal}
  232. ></ActionCell>
  233. );
  234. },
  235. },
  236. ];
  237. const currentPagination = useMemo(() => {
  238. return {
  239. pageIndex: (pagination.current || 1) - 1,
  240. pageSize: pagination.pageSize || 10,
  241. };
  242. }, [pagination]);
  243. const table = useReactTable({
  244. data: files || [],
  245. columns,
  246. onSortingChange: setSorting,
  247. onColumnFiltersChange: setColumnFilters,
  248. getCoreRowModel: getCoreRowModel(),
  249. // getPaginationRowModel: getPaginationRowModel(),
  250. getSortedRowModel: getSortedRowModel(),
  251. getFilteredRowModel: getFilteredRowModel(),
  252. onColumnVisibilityChange: setColumnVisibility,
  253. onRowSelectionChange: setRowSelection,
  254. onPaginationChange: (updaterOrValue: any) => {
  255. if (typeof updaterOrValue === 'function') {
  256. const nextPagination = updaterOrValue(currentPagination);
  257. setPagination({
  258. page: nextPagination.pageIndex + 1,
  259. pageSize: nextPagination.pageSize,
  260. });
  261. } else {
  262. setPagination({
  263. page: updaterOrValue.pageIndex,
  264. pageSize: updaterOrValue.pageSize,
  265. });
  266. }
  267. },
  268. manualPagination: true, //we're doing manual "server-side" pagination
  269. state: {
  270. sorting,
  271. columnFilters,
  272. columnVisibility,
  273. rowSelection,
  274. pagination: currentPagination,
  275. },
  276. rowCount: total ?? 0,
  277. debugTable: true,
  278. });
  279. return (
  280. <div className="w-full">
  281. <div className="rounded-md border">
  282. <Table>
  283. <TableHeader>
  284. {table.getHeaderGroups().map((headerGroup) => (
  285. <TableRow key={headerGroup.id}>
  286. {headerGroup.headers.map((header) => {
  287. return (
  288. <TableHead key={header.id}>
  289. {header.isPlaceholder
  290. ? null
  291. : flexRender(
  292. header.column.columnDef.header,
  293. header.getContext(),
  294. )}
  295. </TableHead>
  296. );
  297. })}
  298. </TableRow>
  299. ))}
  300. </TableHeader>
  301. <TableBody>
  302. {loading ? (
  303. <TableSkeleton columnsLength={columns.length}></TableSkeleton>
  304. ) : table.getRowModel().rows?.length ? (
  305. table.getRowModel().rows.map((row) => (
  306. <TableRow
  307. key={row.id}
  308. data-state={row.getIsSelected() && 'selected'}
  309. >
  310. {row.getVisibleCells().map((cell) => (
  311. <TableCell
  312. key={cell.id}
  313. className={cell.column.columnDef.meta?.cellClassName}
  314. >
  315. {flexRender(
  316. cell.column.columnDef.cell,
  317. cell.getContext(),
  318. )}
  319. </TableCell>
  320. ))}
  321. </TableRow>
  322. ))
  323. ) : (
  324. <TableEmpty columnsLength={columns.length}></TableEmpty>
  325. )}
  326. </TableBody>
  327. </Table>
  328. </div>
  329. <div className="flex items-center justify-end space-x-2 py-4">
  330. <div className="flex-1 text-sm text-muted-foreground">
  331. {table.getFilteredSelectedRowModel().rows.length} of {total} row(s)
  332. selected.
  333. </div>
  334. <div className="space-x-2">
  335. <Button
  336. variant="outline"
  337. size="sm"
  338. onClick={() => table.previousPage()}
  339. disabled={!table.getCanPreviousPage()}
  340. >
  341. Previous
  342. </Button>
  343. <Button
  344. variant="outline"
  345. size="sm"
  346. onClick={() => table.nextPage()}
  347. disabled={!table.getCanNextPage()}
  348. >
  349. Next
  350. </Button>
  351. </div>
  352. </div>
  353. {connectToKnowledgeVisible && (
  354. <LinkToDatasetDialog
  355. hideModal={hideConnectToKnowledgeModal}
  356. initialConnectedIds={initialConnectedIds}
  357. onConnectToKnowledgeOk={onConnectToKnowledgeOk}
  358. loading={connectToKnowledgeLoading}
  359. ></LinkToDatasetDialog>
  360. )}
  361. {fileRenameVisible && (
  362. <RenameDialog
  363. hideModal={hideFileRenameModal}
  364. onOk={onFileRenameOk}
  365. initialName={initialFileName}
  366. loading={fileRenameLoading}
  367. ></RenameDialog>
  368. )}
  369. </div>
  370. );
  371. }