mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-04 02:20:38 +08:00
### What problem does this PR solve? Feat: Batch operations on documents in a dataset #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
43e507d554
commit
6a45d93005
@ -1,13 +1,15 @@
|
||||
import { Toaster as Sonner } from '@/components/ui/sonner';
|
||||
import { Toaster } from '@/components/ui/toaster';
|
||||
import i18n from '@/locales/config';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { App, ConfigProvider, ConfigProviderProps, theme } from 'antd';
|
||||
import pt_BR from 'antd/lib/locale/pt_BR';
|
||||
import deDE from 'antd/locale/de_DE';
|
||||
import enUS from 'antd/locale/en_US';
|
||||
import vi_VN from 'antd/locale/vi_VN';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import zh_HK from 'antd/locale/zh_HK';
|
||||
import deDE from 'antd/locale/de_DE';
|
||||
import dayjs from 'dayjs';
|
||||
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
@ -67,6 +69,8 @@ function Root({ children }: React.PropsWithChildren) {
|
||||
locale={locale}
|
||||
>
|
||||
<App>{children}</App>
|
||||
<Sonner position={'top-right'} expand richColors closeButton></Sonner>
|
||||
<Toaster />
|
||||
</ConfigProvider>
|
||||
<ReactQueryDevtools buttonPosition={'top-left'} />
|
||||
</>
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { PropsWithChildren, useCallback, useEffect } from 'react';
|
||||
import { PropsWithChildren, useCallback, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ZodArray, ZodString, z } from 'zod';
|
||||
|
||||
@ -24,12 +24,14 @@ export type CheckboxFormMultipleProps = {
|
||||
filters?: FilterCollection[];
|
||||
value?: FilterValue;
|
||||
onChange?: FilterChange;
|
||||
setOpen(open: boolean): void;
|
||||
};
|
||||
|
||||
function CheckboxFormMultiple({
|
||||
filters = [],
|
||||
value,
|
||||
onChange,
|
||||
setOpen,
|
||||
}: CheckboxFormMultipleProps) {
|
||||
const fieldsDict = filters?.reduce<Record<string, Array<any>>>((pre, cur) => {
|
||||
pre[cur.field] = [];
|
||||
@ -53,14 +55,14 @@ function CheckboxFormMultiple({
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
||||
// setOwnerIds(data.items);
|
||||
onChange?.(data);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
onChange?.(fieldsDict);
|
||||
}, [fieldsDict, onChange]);
|
||||
setOpen(false);
|
||||
}, [fieldsDict, onChange, setOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
form.reset(value);
|
||||
@ -148,14 +150,17 @@ export function FilterPopover({
|
||||
onChange,
|
||||
filters,
|
||||
}: PropsWithChildren & CheckboxFormMultipleProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<CheckboxFormMultiple
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
filters={filters}
|
||||
setOpen={setOpen}
|
||||
></CheckboxFormMultiple>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
29
web/src/hooks/logic-hooks/use-row-selection.ts
Normal file
29
web/src/hooks/logic-hooks/use-row-selection.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { RowSelectionState } from '@tanstack/react-table';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
export function useRowSelection() {
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
return {
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
rowSelectionIsEmpty: isEmpty(rowSelection),
|
||||
};
|
||||
}
|
||||
|
||||
export type UseRowSelectionType = ReturnType<typeof useRowSelection>;
|
||||
|
||||
export function useSelectedIds<T extends Array<{ id: string }>>(
|
||||
rowSelection: RowSelectionState,
|
||||
list: T,
|
||||
) {
|
||||
const selectedIds = useMemo(() => {
|
||||
const indexes = Object.keys(rowSelection);
|
||||
return list
|
||||
.filter((x, idx) => indexes.some((y) => Number(y) === idx))
|
||||
.map((x) => x.id);
|
||||
}, [list, rowSelection]);
|
||||
|
||||
return { selectedIds };
|
||||
}
|
@ -4,9 +4,6 @@ import { Outlet } from 'umi';
|
||||
import '../locales/config';
|
||||
import Header from './components/header';
|
||||
|
||||
import { Toaster as Sonner } from '@/components/ui/sonner';
|
||||
import { Toaster } from '@/components/ui/toaster';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const { Content } = Layout;
|
||||
@ -32,8 +29,6 @@ const App: React.FC = () => {
|
||||
>
|
||||
<Outlet />
|
||||
</Content>
|
||||
<Toaster />
|
||||
<Sonner position={'top-right'} expand richColors closeButton></Sonner>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { getExtension } from '@/utils/document-util';
|
||||
import { useMemo } from 'react';
|
||||
@ -36,12 +37,15 @@ import { useSaveMeta } from './use-save-meta';
|
||||
export type DatasetTableProps = Pick<
|
||||
ReturnType<typeof useFetchDocumentList>,
|
||||
'documents' | 'setPagination' | 'pagination'
|
||||
>;
|
||||
> &
|
||||
Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'>;
|
||||
|
||||
export function DatasetTable({
|
||||
documents,
|
||||
pagination,
|
||||
setPagination,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
}: DatasetTableProps) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
@ -49,7 +53,6 @@ export function DatasetTable({
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
|
||||
const {
|
||||
changeParserLoading,
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -28,7 +29,7 @@ export default function Dataset() {
|
||||
onDocumentUploadOk,
|
||||
documentUploadLoading,
|
||||
} = useHandleUploadDocument();
|
||||
const { list } = useBulkOperateDataset();
|
||||
|
||||
const {
|
||||
searchString,
|
||||
documents,
|
||||
@ -48,6 +49,15 @@ export default function Dataset() {
|
||||
showCreateModal,
|
||||
} = useCreateEmptyDocument();
|
||||
|
||||
const { rowSelection, rowSelectionIsEmpty, setRowSelection } =
|
||||
useRowSelection();
|
||||
|
||||
const { list } = useBulkOperateDataset({
|
||||
documents,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="p-8">
|
||||
<ListFilterBar
|
||||
@ -76,11 +86,13 @@ export default function Dataset() {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ListFilterBar>
|
||||
<BulkOperateBar list={list}></BulkOperateBar>
|
||||
{rowSelectionIsEmpty || <BulkOperateBar list={list}></BulkOperateBar>}
|
||||
<DatasetTable
|
||||
documents={documents}
|
||||
pagination={pagination}
|
||||
setPagination={setPagination}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
></DatasetTable>
|
||||
{documentUploadVisible && (
|
||||
<FileUploadDialog
|
||||
|
@ -1,39 +1,131 @@
|
||||
import {
|
||||
UseRowSelectionType,
|
||||
useSelectedIds,
|
||||
} from '@/hooks/logic-hooks/use-row-selection';
|
||||
import {
|
||||
useRemoveDocument,
|
||||
useRunDocument,
|
||||
useSetDocumentStatus,
|
||||
} from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { Ban, CircleCheck, CircleX, Play, Trash2 } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import { DocumentType, RunningStatus } from './constant';
|
||||
|
||||
export function useBulkOperateDataset() {
|
||||
export function useBulkOperateDataset({
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
documents,
|
||||
}: Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'> & {
|
||||
documents: IDocumentInfo[];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { selectedIds: selectedRowKeys } = useSelectedIds(
|
||||
rowSelection,
|
||||
documents,
|
||||
);
|
||||
|
||||
const { runDocumentByIds } = useRunDocument();
|
||||
const { setDocumentStatus } = useSetDocumentStatus();
|
||||
const { removeDocument } = useRemoveDocument();
|
||||
|
||||
const runDocument = useCallback(
|
||||
(run: number) => {
|
||||
const nonVirtualKeys = selectedRowKeys.filter(
|
||||
(x) =>
|
||||
!documents.some((y) => x === y.id && y.type === DocumentType.Virtual),
|
||||
);
|
||||
|
||||
if (nonVirtualKeys.length === 0) {
|
||||
toast.error(t('Please select a non-empty file list'));
|
||||
return;
|
||||
}
|
||||
runDocumentByIds({
|
||||
documentIds: nonVirtualKeys,
|
||||
run,
|
||||
shouldDelete: false,
|
||||
});
|
||||
},
|
||||
[documents, runDocumentByIds, selectedRowKeys, t],
|
||||
);
|
||||
|
||||
const handleRunClick = useCallback(() => {
|
||||
runDocument(1);
|
||||
}, [runDocument]);
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
runDocument(2);
|
||||
}, [runDocument]);
|
||||
|
||||
const onChangeStatus = useCallback(
|
||||
(enabled: boolean) => {
|
||||
selectedRowKeys.forEach((id) => {
|
||||
setDocumentStatus({ status: enabled, documentId: id });
|
||||
});
|
||||
},
|
||||
[selectedRowKeys, setDocumentStatus],
|
||||
);
|
||||
|
||||
const handleEnableClick = useCallback(() => {
|
||||
onChangeStatus(true);
|
||||
}, [onChangeStatus]);
|
||||
|
||||
const handleDisableClick = useCallback(() => {
|
||||
onChangeStatus(false);
|
||||
}, [onChangeStatus]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
const deletedKeys = selectedRowKeys.filter(
|
||||
(x) =>
|
||||
!documents
|
||||
.filter((y) => y.run === RunningStatus.RUNNING)
|
||||
.some((y) => y.id === x),
|
||||
);
|
||||
if (deletedKeys.length === 0) {
|
||||
toast.error(t('theDocumentBeingParsedCannotBeDeleted'));
|
||||
return;
|
||||
}
|
||||
|
||||
return removeDocument(deletedKeys);
|
||||
}, [selectedRowKeys, removeDocument, documents, t]);
|
||||
|
||||
const list = [
|
||||
{
|
||||
id: 'enabled',
|
||||
label: t('knowledgeDetails.enabled'),
|
||||
icon: <CircleCheck />,
|
||||
onClick: () => {},
|
||||
onClick: handleEnableClick,
|
||||
},
|
||||
{
|
||||
id: 'disabled',
|
||||
label: t('knowledgeDetails.disabled'),
|
||||
icon: <Ban />,
|
||||
onClick: () => {},
|
||||
onClick: handleDisableClick,
|
||||
},
|
||||
{
|
||||
id: 'run',
|
||||
label: t('knowledgeDetails.run'),
|
||||
icon: <Play />,
|
||||
onClick: () => {},
|
||||
onClick: handleRunClick,
|
||||
},
|
||||
{
|
||||
id: 'cancel',
|
||||
label: t('knowledgeDetails.cancel'),
|
||||
icon: <CircleX />,
|
||||
onClick: () => {},
|
||||
onClick: handleCancelClick,
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
label: t('common.delete'),
|
||||
icon: <Trash2 />,
|
||||
onClick: () => {},
|
||||
onClick: async () => {
|
||||
const code = await handleDelete();
|
||||
if (code === 0) {
|
||||
setRowSelection({});
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useSecondPathName } from '@/hooks/route-hook';
|
||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { Banknote, LayoutGrid, User } from 'lucide-react';
|
||||
import { useHandleMenuClick } from './hooks';
|
||||
|
||||
@ -16,15 +18,6 @@ const items = [
|
||||
{ icon: Banknote, label: 'Settings', key: Routes.DatasetSetting },
|
||||
];
|
||||
|
||||
const dataset = {
|
||||
id: 1,
|
||||
title: 'Legal knowledge base',
|
||||
files: '1,242 files',
|
||||
size: '152 MB',
|
||||
created: '12.02.2024',
|
||||
image: 'https://github.com/shadcn.png',
|
||||
};
|
||||
|
||||
export function SideBar() {
|
||||
const pathName = useSecondPathName();
|
||||
const { handleMenuClick } = useHandleMenuClick();
|
||||
@ -33,16 +26,18 @@ export function SideBar() {
|
||||
return (
|
||||
<aside className="w-60 relative border-r ">
|
||||
<div className="p-6 space-y-2 border-b">
|
||||
<div
|
||||
className="w-[70px] h-[70px] rounded-xl bg-cover"
|
||||
style={{ backgroundImage: `url(${dataset.image})` }}
|
||||
/>
|
||||
<Avatar className="size-20 rounded-lg">
|
||||
<AvatarImage src={data.avatar} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<h3 className="text-lg font-semibold mb-2">{data.name}</h3>
|
||||
<div className="text-sm opacity-80">
|
||||
{dataset.files} | {dataset.size}
|
||||
{data.doc_num} files | {data.chunk_num} chunks
|
||||
</div>
|
||||
<div className="text-sm opacity-80">
|
||||
Created {formatDate(data.create_time)}
|
||||
</div>
|
||||
<div className="text-sm opacity-80">Created {dataset.created}</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
{items.map((item, itemIdx) => {
|
||||
|
@ -1,149 +0,0 @@
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { PropsWithChildren, useCallback, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request';
|
||||
import { useSelectOwners } from './use-select-owners';
|
||||
|
||||
const FormSchema = z.object({
|
||||
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: 'You have to select at least one item.',
|
||||
}),
|
||||
});
|
||||
|
||||
type CheckboxReactHookFormMultipleProps = Pick<
|
||||
ReturnType<typeof useFetchNextKnowledgeListByPage>,
|
||||
'setOwnerIds' | 'ownerIds'
|
||||
>;
|
||||
|
||||
function CheckboxReactHookFormMultiple({
|
||||
setOwnerIds,
|
||||
ownerIds,
|
||||
}: CheckboxReactHookFormMultipleProps) {
|
||||
const owners = useSelectOwners();
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
items: [],
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
setOwnerIds(data.items);
|
||||
}
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
setOwnerIds([]);
|
||||
}, [setOwnerIds]);
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue('items', ownerIds);
|
||||
}, [form, ownerIds]);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8"
|
||||
onReset={() => form.reset()}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<div className="mb-4">
|
||||
<FormLabel className="text-base">Owner</FormLabel>
|
||||
</div>
|
||||
{owners.map((item) => (
|
||||
<FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex flex-row space-x-3 space-y-0 items-center"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(item.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, item.id])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== item.id,
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-lg">
|
||||
{item.label}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<span className=" text-sm">{item.count}</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<Button
|
||||
type="button"
|
||||
variant={'outline'}
|
||||
size={'sm'}
|
||||
onClick={onReset}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
<Button type="submit" size={'sm'}>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export function DatasetsFilterPopover({
|
||||
children,
|
||||
setOwnerIds,
|
||||
ownerIds,
|
||||
}: PropsWithChildren & CheckboxReactHookFormMultipleProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<CheckboxReactHookFormMultiple
|
||||
setOwnerIds={setOwnerIds}
|
||||
ownerIds={ownerIds}
|
||||
></CheckboxReactHookFormMultiple>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
@ -3,8 +3,6 @@
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
OnChangeFn,
|
||||
RowSelectionState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
@ -35,6 +33,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchFileList } from '@/hooks/use-file-request';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
import { cn } from '@/lib/utils';
|
||||
@ -52,10 +51,9 @@ import { useNavigateToOtherFolder } from './use-navigate-to-folder';
|
||||
type FilesTableProps = Pick<
|
||||
ReturnType<typeof useFetchFileList>,
|
||||
'files' | 'loading' | 'pagination' | 'setPagination' | 'total'
|
||||
> & {
|
||||
rowSelection: RowSelectionState;
|
||||
setRowSelection: OnChangeFn<RowSelectionState>;
|
||||
} & UseMoveDocumentShowType;
|
||||
> &
|
||||
Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'> &
|
||||
UseMoveDocumentShowType;
|
||||
|
||||
export function FilesTable({
|
||||
files,
|
||||
|
@ -9,11 +9,9 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchFileList } from '@/hooks/use-file-request';
|
||||
import { RowSelectionState } from '@tanstack/react-table';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CreateFolderDialog } from './create-folder-dialog';
|
||||
import { FileBreadcrumb } from './file-breadcrumb';
|
||||
@ -60,7 +58,8 @@ export default function Files() {
|
||||
moveFileLoading,
|
||||
} = useHandleMoveFile();
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const { rowSelection, setRowSelection, rowSelectionIsEmpty } =
|
||||
useRowSelection();
|
||||
|
||||
const { list } = useBulkOperateFile({
|
||||
files,
|
||||
@ -101,7 +100,7 @@ export default function Files() {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ListFilterBar>
|
||||
{!isEmpty(rowSelection) && <BulkOperateBar list={list}></BulkOperateBar>}
|
||||
{!rowSelectionIsEmpty && <BulkOperateBar list={list}></BulkOperateBar>}
|
||||
<FilesTable
|
||||
files={files}
|
||||
total={total}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useSelectedIds } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
import { OnChangeFn, RowSelectionState } from '@tanstack/react-table';
|
||||
import { FolderInput, Trash2 } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHandleDeleteFile } from './use-delete-file';
|
||||
import { UseMoveDocumentShowType } from './use-move-file';
|
||||
@ -18,12 +18,7 @@ export function useBulkOperateFile({
|
||||
} & UseMoveDocumentShowType) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const selectedIds = useMemo(() => {
|
||||
const indexes = Object.keys(rowSelection);
|
||||
return files
|
||||
.filter((x, idx) => indexes.some((y) => Number(y) === idx))
|
||||
.map((x) => x.id);
|
||||
}, [files, rowSelection]);
|
||||
const { selectedIds } = useSelectedIds(rowSelection, files);
|
||||
|
||||
const { handleRemoveFile } = useHandleDeleteFile();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user