mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-17 18:35:19 +02:00
feat: implement document deletion functionality and streamline column visibility management in DocumentsTable
This commit is contained in:
parent
d5fd4c2863
commit
e615a6478c
3 changed files with 50 additions and 68 deletions
|
|
@ -4,7 +4,6 @@ import { useSetAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
CircleAlert,
|
CircleAlert,
|
||||||
CircleX,
|
CircleX,
|
||||||
Columns3,
|
|
||||||
FilePlus2,
|
FilePlus2,
|
||||||
FileType,
|
FileType,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
|
|
@ -31,11 +30,9 @@ import {
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
||||||
import { getDocumentTypeIcon, getDocumentTypeLabel } from "./DocumentTypeIcon";
|
import { getDocumentTypeIcon, getDocumentTypeLabel } from "./DocumentTypeIcon";
|
||||||
import type { ColumnVisibility } from "./types";
|
|
||||||
|
|
||||||
export function DocumentsFilters({
|
export function DocumentsFilters({
|
||||||
typeCounts: typeCountsRecord,
|
typeCounts: typeCountsRecord,
|
||||||
|
|
@ -45,8 +42,6 @@ export function DocumentsFilters({
|
||||||
onBulkDelete,
|
onBulkDelete,
|
||||||
onToggleType,
|
onToggleType,
|
||||||
activeTypes,
|
activeTypes,
|
||||||
columnVisibility,
|
|
||||||
onToggleColumn,
|
|
||||||
}: {
|
}: {
|
||||||
typeCounts: Partial<Record<DocumentTypeEnum, number>>;
|
typeCounts: Partial<Record<DocumentTypeEnum, number>>;
|
||||||
selectedIds: Set<number>;
|
selectedIds: Set<number>;
|
||||||
|
|
@ -55,8 +50,6 @@ export function DocumentsFilters({
|
||||||
onBulkDelete: () => Promise<void>;
|
onBulkDelete: () => Promise<void>;
|
||||||
onToggleType: (type: DocumentTypeEnum, checked: boolean) => void;
|
onToggleType: (type: DocumentTypeEnum, checked: boolean) => void;
|
||||||
activeTypes: DocumentTypeEnum[];
|
activeTypes: DocumentTypeEnum[];
|
||||||
columnVisibility: ColumnVisibility;
|
|
||||||
onToggleColumn: (id: keyof ColumnVisibility, checked: boolean) => void;
|
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslations("documents");
|
const t = useTranslations("documents");
|
||||||
const id = React.useId();
|
const id = React.useId();
|
||||||
|
|
@ -252,57 +245,7 @@ export function DocumentsFilters({
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
{/* View/Columns Popover */}
|
{/* Bulk Delete Button */}
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-9 gap-2 border-dashed border-border/60 text-muted-foreground hover:text-foreground hover:border-border"
|
|
||||||
>
|
|
||||||
<Columns3 size={14} className="text-muted-foreground" />
|
|
||||||
<span className="hidden sm:inline">View</span>
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-36 !p-0 overflow-hidden" align="end">
|
|
||||||
<div className="px-2.5 pt-3 pb-2">
|
|
||||||
<div className="mb-1.5 px-1 text-[11px] font-medium text-muted-foreground">
|
|
||||||
Toggle columns
|
|
||||||
</div>
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
{(
|
|
||||||
[
|
|
||||||
["document_type", "Source"],
|
|
||||||
["created_by", "User"],
|
|
||||||
["created_at", "Created"],
|
|
||||||
] as Array<[keyof ColumnVisibility, string]>
|
|
||||||
).map(([key, label], i) => (
|
|
||||||
<button
|
|
||||||
key={key}
|
|
||||||
type="button"
|
|
||||||
className="flex w-full items-center gap-2 py-1 px-2.5 rounded-md hover:bg-muted/50 transition-colors cursor-pointer text-left"
|
|
||||||
onClick={() => onToggleColumn(key, !columnVisibility[key])}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
id={`${id}-col-${i}`}
|
|
||||||
checked={columnVisibility[key]}
|
|
||||||
onCheckedChange={(checked: boolean) => onToggleColumn(key, !!checked)}
|
|
||||||
className="h-3.5 w-3.5 flex-shrink-0 data-[state=checked]:bg-primary data-[state=checked]:border-primary"
|
|
||||||
/>
|
|
||||||
<Label
|
|
||||||
htmlFor={`${id}-col-${i}`}
|
|
||||||
className="flex flex-1 items-center gap-2 font-normal text-xs cursor-pointer min-w-0"
|
|
||||||
>
|
|
||||||
<span className="truncate min-w-0">{label}</span>
|
|
||||||
</Label>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
{/* Bulk Delete Button - positioned next to View on mobile */}
|
|
||||||
{selectedIds.size > 0 && (
|
{selectedIds.size > 0 && (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import {
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
||||||
import { DocumentTypeChip } from "./DocumentTypeIcon";
|
import { DocumentTypeChip } from "./DocumentTypeIcon";
|
||||||
|
import { RowActions } from "./RowActions";
|
||||||
import type { ColumnVisibility, Document } from "./types";
|
import type { ColumnVisibility, Document } from "./types";
|
||||||
|
|
||||||
export type SortKey = keyof Pick<Document, "title" | "document_type" | "created_at">;
|
export type SortKey = keyof Pick<Document, "title" | "document_type" | "created_at">;
|
||||||
|
|
@ -142,6 +143,8 @@ export function DocumentsTableShell({
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDesc,
|
sortDesc,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
|
deleteDocument,
|
||||||
|
searchSpaceId,
|
||||||
}: {
|
}: {
|
||||||
documents: Document[];
|
documents: Document[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
|
@ -153,6 +156,8 @@ export function DocumentsTableShell({
|
||||||
sortKey: SortKey;
|
sortKey: SortKey;
|
||||||
sortDesc: boolean;
|
sortDesc: boolean;
|
||||||
onSortChange: (key: SortKey) => void;
|
onSortChange: (key: SortKey) => void;
|
||||||
|
deleteDocument: (id: number) => Promise<boolean>;
|
||||||
|
searchSpaceId: string;
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslations("documents");
|
const t = useTranslations("documents");
|
||||||
const { openDialog } = useDocumentUploadDialog();
|
const { openDialog } = useDocumentUploadDialog();
|
||||||
|
|
@ -273,7 +278,7 @@ export function DocumentsTableShell({
|
||||||
<Table className="table-fixed w-full">
|
<Table className="table-fixed w-full">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow className="hover:bg-transparent border-b border-border/40">
|
<TableRow className="hover:bg-transparent border-b border-border/40">
|
||||||
<TableHead className="w-8 px-0 text-center border-r border-border/40">
|
<TableHead className="w-8 px-0 text-center">
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<Skeleton className="h-4 w-4 rounded" />
|
<Skeleton className="h-4 w-4 rounded" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -296,6 +301,9 @@ export function DocumentsTableShell({
|
||||||
<Skeleton className="h-3 w-16" />
|
<Skeleton className="h-3 w-16" />
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
|
<TableHead className="w-10">
|
||||||
|
<span className="sr-only">Actions</span>
|
||||||
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
@ -307,7 +315,7 @@ export function DocumentsTableShell({
|
||||||
key={`skeleton-${index}`}
|
key={`skeleton-${index}`}
|
||||||
className="border-b border-border/40 hover:bg-transparent"
|
className="border-b border-border/40 hover:bg-transparent"
|
||||||
>
|
>
|
||||||
<TableCell className="w-8 px-0 py-2.5 text-center border-r border-border/40">
|
<TableCell className="w-8 px-0 py-2.5 text-center">
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<Skeleton className="h-4 w-4 rounded" />
|
<Skeleton className="h-4 w-4 rounded" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -333,6 +341,9 @@ export function DocumentsTableShell({
|
||||||
<Skeleton className="h-4 w-20" />
|
<Skeleton className="h-4 w-20" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
)}
|
||||||
|
<TableCell className="w-10 py-2.5 text-center">
|
||||||
|
<Skeleton className="h-6 w-6 mx-auto rounded" />
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
@ -406,7 +417,7 @@ export function DocumentsTableShell({
|
||||||
<Table className="table-fixed w-full">
|
<Table className="table-fixed w-full">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow className="hover:bg-transparent border-b border-border/40">
|
<TableRow className="hover:bg-transparent border-b border-border/40">
|
||||||
<TableHead className="w-8 px-0 text-center border-r border-border/40">
|
<TableHead className="w-8 px-0 text-center">
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={allSelectedOnPage || (someSelectedOnPage && "indeterminate")}
|
checked={allSelectedOnPage || (someSelectedOnPage && "indeterminate")}
|
||||||
|
|
@ -461,6 +472,9 @@ export function DocumentsTableShell({
|
||||||
</SortableHeader>
|
</SortableHeader>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
|
<TableHead className="w-10">
|
||||||
|
<span className="sr-only">Actions</span>
|
||||||
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
@ -488,7 +502,7 @@ export function DocumentsTableShell({
|
||||||
: "hover:bg-muted/30"
|
: "hover:bg-muted/30"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<TableCell className="w-8 px-0 py-2.5 text-center border-r border-border/40">
|
<TableCell className="w-8 px-0 py-2.5 text-center">
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
|
|
@ -549,6 +563,13 @@ export function DocumentsTableShell({
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
)}
|
||||||
|
<TableCell className="w-10 py-2.5 text-center">
|
||||||
|
<RowActions
|
||||||
|
document={doc}
|
||||||
|
deleteDocument={deleteDocument}
|
||||||
|
searchSpaceId={searchSpaceId}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
</motion.tr>
|
</motion.tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -626,6 +647,11 @@ export function DocumentsTableShell({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<RowActions
|
||||||
|
document={doc}
|
||||||
|
deleteDocument={deleteDocument}
|
||||||
|
searchSpaceId={searchSpaceId}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -138,10 +138,6 @@ export default function DocumentsTable() {
|
||||||
setPageIndex(0);
|
setPageIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onToggleColumn = (id: keyof ColumnVisibility, checked: boolean) => {
|
|
||||||
setColumnVisibility((prev) => ({ ...prev, [id]: checked }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
|
||||||
const refreshCurrentView = useCallback(async () => {
|
const refreshCurrentView = useCallback(async () => {
|
||||||
|
|
@ -193,6 +189,23 @@ export default function DocumentsTable() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Single document delete handler for RowActions
|
||||||
|
const handleDeleteDocument = useCallback(async (id: number): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await deleteDocumentMutation({ id });
|
||||||
|
toast.success(t("delete_success") || "Document deleted");
|
||||||
|
// If in search mode, refetch search results to reflect deletion
|
||||||
|
if (isSearchMode) {
|
||||||
|
await refetchSearch();
|
||||||
|
}
|
||||||
|
// Real-time mode: Electric will sync the deletion automatically
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error deleting document:", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, [deleteDocumentMutation, isSearchMode, refetchSearch, t]);
|
||||||
|
|
||||||
const handleSortChange = useCallback((key: SortKey) => {
|
const handleSortChange = useCallback((key: SortKey) => {
|
||||||
setSortKey((currentKey) => {
|
setSortKey((currentKey) => {
|
||||||
if (currentKey === key) {
|
if (currentKey === key) {
|
||||||
|
|
@ -237,8 +250,6 @@ export default function DocumentsTable() {
|
||||||
onBulkDelete={onBulkDelete}
|
onBulkDelete={onBulkDelete}
|
||||||
onToggleType={onToggleType}
|
onToggleType={onToggleType}
|
||||||
activeTypes={activeTypes}
|
activeTypes={activeTypes}
|
||||||
columnVisibility={columnVisibility}
|
|
||||||
onToggleColumn={onToggleColumn}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
|
|
@ -253,6 +264,8 @@ export default function DocumentsTable() {
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDesc={sortDesc}
|
sortDesc={sortDesc}
|
||||||
onSortChange={handleSortChange}
|
onSortChange={handleSortChange}
|
||||||
|
deleteDocument={handleDeleteDocument}
|
||||||
|
searchSpaceId={String(searchSpaceId)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue